Spaces:
Paused
Paused
Update src/streamlit_app.py
Browse files사용자 투자 성향을 파이차트 위에도 적어주기(Srisk랑 같이)
재생성 버튼 두 개 되는 오류 수정
- src/streamlit_app.py +42 -22
src/streamlit_app.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
| 1 |
import os
|
|
|
|
| 2 |
os.environ['STREAMLIT_HOME'] = '/tmp/streamlit'
|
| 3 |
|
| 4 |
os.environ['HF_HOME'] = '/tmp/hf_cache'
|
|
@@ -89,10 +90,17 @@ def generate_llm_reports(portfolio_list, override_style=None):
|
|
| 89 |
if override_style:
|
| 90 |
investor_style = override_style
|
| 91 |
st.toast(f"'{investor_style}' 스타일(수동)로 재생성 시작...")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
else:
|
| 93 |
srisk, investor_style = classify_investment_style(full_market_df, portfolio_list)
|
| 94 |
st.toast(f"srisk: {srisk:.2f} '{investor_style}' 스타일(자동)로 생성 시작...")
|
| 95 |
-
|
|
|
|
|
|
|
|
|
|
| 96 |
reports = {}
|
| 97 |
|
| 98 |
for item in portfolio_list:
|
|
@@ -193,23 +201,24 @@ def generate_llm_report(investor_style, ticker):
|
|
| 193 |
return result
|
| 194 |
|
| 195 |
|
| 196 |
-
# ----------------------------------------------------------------------
|
| 197 |
# 1. 세션 상태(Session State) 초기화
|
| 198 |
-
#
|
| 199 |
-
# st.session_state : 스트림릿이 재실행되어도 값을 유지하는 마법의 변수
|
| 200 |
if 'portfolio' not in st.session_state:
|
| 201 |
st.session_state.portfolio = [] # 사용자의 포트폴리오를 저장할 리스트
|
| 202 |
if 'last_report' not in st.session_state:
|
| 203 |
st.session_state.last_report = None # 생성된 보고서를 저장할 변수
|
| 204 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 205 |
if 'peft_model' not in st.session_state:
|
| 206 |
st.session_state.peft_model = None
|
| 207 |
if 'tokenizer' not in st.session_state:
|
| 208 |
st.session_state.tokenizer = None
|
| 209 |
|
| 210 |
-
# ----------------------------------------------------------------------
|
| 211 |
# 2. 페이지 기본 설정
|
| 212 |
-
# ----------------------------------------------------------------------
|
| 213 |
st.set_page_config(page_title="AI 주식 포트폴리오 분석", layout="wide")
|
| 214 |
st.title("🤖 AI 주식 포트폴리오 보고서 생성기")
|
| 215 |
st.write("NASDAQ 100 종목을 검색하여 포트폴리오를 구성하고, 맞춤형 AI 보고서를 받아보세요.")
|
|
@@ -218,9 +227,7 @@ TICKER_OPTIONS_LIST, DISPLAY_TO_TICKER_MAP, TICKER_TO_PRICE_MAP = load_ticker_da
|
|
| 218 |
company = load_company_metrics()
|
| 219 |
full_market_df = load_full_df() # (Srisk 모듈용 데이터)
|
| 220 |
|
| 221 |
-
# ----------------------------------------------------------------------
|
| 222 |
# 3. 입력 섹션 (종목 추가)
|
| 223 |
-
# ----------------------------------------------------------------------
|
| 224 |
st.subheader("1. 보유 종목 추가하기")
|
| 225 |
|
| 226 |
# 컬럼을 나눠서 UI를 깔끔하게 구성
|
|
@@ -255,15 +262,34 @@ if st.button("➕ 포트폴리오에 추가", use_container_width=True):
|
|
| 255 |
else:
|
| 256 |
st.warning("종목, 수량을 모두 올바르게 입력하세요.")
|
| 257 |
|
| 258 |
-
# ----------------------------------------------------------------------
|
| 259 |
# 4. 포트폴리오 요약 및 보고서 생성 (스케치 레이아웃)
|
| 260 |
-
# ----------------------------------------------------------------------
|
| 261 |
st.subheader("2. 포트폴리오 요약 및 보고서 생성")
|
| 262 |
-
|
| 263 |
col_chart, col_controls = st.columns(2, gap="large")
|
| 264 |
|
| 265 |
with col_chart:
|
| 266 |
st.markdown("### 📊 포트폴리오 구성")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 267 |
if st.session_state.portfolio:
|
| 268 |
df = pd.DataFrame(st.session_state.portfolio)
|
| 269 |
|
|
@@ -299,7 +325,6 @@ with col_controls:
|
|
| 299 |
)
|
| 300 |
|
| 301 |
if not df.equals(edited_df):
|
| 302 |
-
# 삭제되거나 수정된 DataFrame을 다시 세션 상태(list of dicts)로 변환
|
| 303 |
st.session_state.portfolio = edited_df.to_dict('records')
|
| 304 |
st.toast("포트폴리오가 수정(삭제)되었습니다.")
|
| 305 |
st.rerun()
|
|
@@ -307,12 +332,14 @@ with col_controls:
|
|
| 307 |
if st.button("🔄 포트폴리오 전체 초기화", use_container_width=True, type="secondary"):
|
| 308 |
st.session_state.portfolio = []
|
| 309 |
st.session_state.last_report = None
|
|
|
|
|
|
|
|
|
|
|
|
|
| 310 |
st.toast("포트폴리오가 초기화되었습니다.")
|
| 311 |
st.rerun()
|
| 312 |
|
| 313 |
-
# ----------------------------------------------------------------------
|
| 314 |
# 5. 보고서 생성 버튼 (메인 LLM 호출)
|
| 315 |
-
# ----------------------------------------------------------------------
|
| 316 |
if st.button("🚀 AI 보고서 생성하기", type="primary", use_container_width=True, disabled=(not st.session_state.portfolio)):
|
| 317 |
if st.session_state.peft_model and st.session_state.tokenizer:
|
| 318 |
with st.spinner("AI가 포트폴리오를 분석하고 보고서를 작성 중입니다..."):
|
|
@@ -324,9 +351,7 @@ with col_controls:
|
|
| 324 |
st.warning("모델이 아직 로드 중입니다. 잠시 후 다시 시도해주세요.")
|
| 325 |
st.divider()
|
| 326 |
|
| 327 |
-
# ----------------------------------------------------------------------
|
| 328 |
# 6. 보고서 재생성 버튼 (메인 LLM 호출)
|
| 329 |
-
# ----------------------------------------------------------------------
|
| 330 |
if st.session_state.last_report:
|
| 331 |
st.markdown("##### 🔄 다른 성향으로 보고서 다시 뽑기")
|
| 332 |
|
|
@@ -341,7 +366,7 @@ with col_controls:
|
|
| 341 |
)
|
| 342 |
|
| 343 |
with col_regen:
|
| 344 |
-
if st.button(f"'{new_style}' 스타일로 재생성", use_container_width=True):
|
| 345 |
with st.spinner(f"'{new_style}' 스타일로 보고서를 다시 작성 중입니다..."):
|
| 346 |
regenerated_reports = generate_llm_reports(
|
| 347 |
st.session_state.portfolio,
|
|
@@ -350,9 +375,7 @@ with col_controls:
|
|
| 350 |
st.session_state.last_report = regenerated_reports
|
| 351 |
st.rerun() # 화면을 즉시 새로고침
|
| 352 |
|
| 353 |
-
# ----------------------------------------------------------------------
|
| 354 |
# 7. 보고서 출력 섹션
|
| 355 |
-
# ----------------------------------------------------------------------
|
| 356 |
st.divider()
|
| 357 |
|
| 358 |
if st.session_state.last_report:
|
|
@@ -368,10 +391,7 @@ if st.session_state.last_report:
|
|
| 368 |
else:
|
| 369 |
st.info("보고서를 생성하면 이 곳에 결과가 표시됩니다.")
|
| 370 |
|
| 371 |
-
# ----------------------------------------------------------------------
|
| 372 |
# 8. (신규) LLM 모델 로딩 (모든 UI를 그린 후 마지막에 실행)
|
| 373 |
-
# ----------------------------------------------------------------------
|
| 374 |
-
|
| 375 |
# peft_model, tokenizer를 st.session_state로 관리
|
| 376 |
if 'peft_model' not in st.session_state:
|
| 377 |
st.session_state.peft_model = None
|
|
|
|
| 1 |
import os
|
| 2 |
+
|
| 3 |
os.environ['STREAMLIT_HOME'] = '/tmp/streamlit'
|
| 4 |
|
| 5 |
os.environ['HF_HOME'] = '/tmp/hf_cache'
|
|
|
|
| 90 |
if override_style:
|
| 91 |
investor_style = override_style
|
| 92 |
st.toast(f"'{investor_style}' 스타일(수동)로 재생성 시작...")
|
| 93 |
+
|
| 94 |
+
st.session_state.investor_style = investor_style
|
| 95 |
+
st.session_state.srisk = None # 수동 선택 시 Srisk는 N/A 처리
|
| 96 |
+
|
| 97 |
else:
|
| 98 |
srisk, investor_style = classify_investment_style(full_market_df, portfolio_list)
|
| 99 |
st.toast(f"srisk: {srisk:.2f} '{investor_style}' 스타일(자동)로 생성 시작...")
|
| 100 |
+
|
| 101 |
+
st.session_state.investor_style = investor_style
|
| 102 |
+
st.session_state.srisk = srisk
|
| 103 |
+
|
| 104 |
reports = {}
|
| 105 |
|
| 106 |
for item in portfolio_list:
|
|
|
|
| 201 |
return result
|
| 202 |
|
| 203 |
|
|
|
|
| 204 |
# 1. 세션 상태(Session State) 초기화
|
| 205 |
+
# st.session_state : 스트림릿이 재실행되어도 값을 유지하는 변수
|
|
|
|
| 206 |
if 'portfolio' not in st.session_state:
|
| 207 |
st.session_state.portfolio = [] # 사용자의 포트폴리오를 저장할 리스트
|
| 208 |
if 'last_report' not in st.session_state:
|
| 209 |
st.session_state.last_report = None # 생성된 보고서를 저장할 변수
|
| 210 |
|
| 211 |
+
if 'srisk' not in st.session_state:
|
| 212 |
+
st.session_state.srisk = None
|
| 213 |
+
if 'investor_style' not in st.session_state:
|
| 214 |
+
st.session_state.investor_style = None
|
| 215 |
+
|
| 216 |
if 'peft_model' not in st.session_state:
|
| 217 |
st.session_state.peft_model = None
|
| 218 |
if 'tokenizer' not in st.session_state:
|
| 219 |
st.session_state.tokenizer = None
|
| 220 |
|
|
|
|
| 221 |
# 2. 페이지 기본 설정
|
|
|
|
| 222 |
st.set_page_config(page_title="AI 주식 포트폴리오 분석", layout="wide")
|
| 223 |
st.title("🤖 AI 주식 포트폴리오 보고서 생성기")
|
| 224 |
st.write("NASDAQ 100 종목을 검색하여 포트폴리오를 구성하고, 맞춤형 AI 보고서를 받아보세요.")
|
|
|
|
| 227 |
company = load_company_metrics()
|
| 228 |
full_market_df = load_full_df() # (Srisk 모듈용 데이터)
|
| 229 |
|
|
|
|
| 230 |
# 3. 입력 섹션 (종목 추가)
|
|
|
|
| 231 |
st.subheader("1. 보유 종목 추가하기")
|
| 232 |
|
| 233 |
# 컬럼을 나눠서 UI를 깔끔하게 구성
|
|
|
|
| 262 |
else:
|
| 263 |
st.warning("종목, 수량을 모두 올바르게 입력하세요.")
|
| 264 |
|
|
|
|
| 265 |
# 4. 포트폴리오 요약 및 보고서 생성 (스케치 레이아웃)
|
|
|
|
| 266 |
st.subheader("2. 포트폴리오 요약 및 보고서 생성")
|
|
|
|
| 267 |
col_chart, col_controls = st.columns(2, gap="large")
|
| 268 |
|
| 269 |
with col_chart:
|
| 270 |
st.markdown("### 📊 포트폴리오 구성")
|
| 271 |
+
|
| 272 |
+
if st.session_state.get("investor_style"):
|
| 273 |
+
style = st.session_state.investor_style
|
| 274 |
+
srisk = st.session_state.srisk
|
| 275 |
+
|
| 276 |
+
# (수정) 스타일에 따라 이모지(색상 동그라미) 추가
|
| 277 |
+
if style == "SAFE":
|
| 278 |
+
style_display = f"🔵 {style}" # 파란색
|
| 279 |
+
elif style == "NEUTRAL":
|
| 280 |
+
style_display = f"🟢 {style}" # 연두색
|
| 281 |
+
elif style == "RISKY":
|
| 282 |
+
style_display = f"🔴 {style}" # 빨간색
|
| 283 |
+
else:
|
| 284 |
+
style_display = style # 기본값
|
| 285 |
+
|
| 286 |
+
if srisk is not None:
|
| 287 |
+
# 자동 분석 (Srisk가 있음)
|
| 288 |
+
st.metric(label="AI 분석 투자 성향", value=style, delta=f"S-Risk: {srisk:.2f}")
|
| 289 |
+
else:
|
| 290 |
+
# 수동 선택 (Srisk가 None)
|
| 291 |
+
st.metric(label="선택된 투자 성향", value=style, delta="수동 선택")
|
| 292 |
+
|
| 293 |
if st.session_state.portfolio:
|
| 294 |
df = pd.DataFrame(st.session_state.portfolio)
|
| 295 |
|
|
|
|
| 325 |
)
|
| 326 |
|
| 327 |
if not df.equals(edited_df):
|
|
|
|
| 328 |
st.session_state.portfolio = edited_df.to_dict('records')
|
| 329 |
st.toast("포트폴리오가 수정(삭제)되었습니다.")
|
| 330 |
st.rerun()
|
|
|
|
| 332 |
if st.button("🔄 포트폴리오 전체 초기화", use_container_width=True, type="secondary"):
|
| 333 |
st.session_state.portfolio = []
|
| 334 |
st.session_state.last_report = None
|
| 335 |
+
|
| 336 |
+
st.session_state.srisk = None
|
| 337 |
+
st.session_state.investor_style = None
|
| 338 |
+
|
| 339 |
st.toast("포트폴리오가 초기화되었습니다.")
|
| 340 |
st.rerun()
|
| 341 |
|
|
|
|
| 342 |
# 5. 보고서 생성 버튼 (메인 LLM 호출)
|
|
|
|
| 343 |
if st.button("🚀 AI 보고서 생성하기", type="primary", use_container_width=True, disabled=(not st.session_state.portfolio)):
|
| 344 |
if st.session_state.peft_model and st.session_state.tokenizer:
|
| 345 |
with st.spinner("AI가 포트폴리오를 분석하고 보고서를 작성 중입니다..."):
|
|
|
|
| 351 |
st.warning("모델이 아직 로드 중입니다. 잠시 후 다시 시도해주세요.")
|
| 352 |
st.divider()
|
| 353 |
|
|
|
|
| 354 |
# 6. 보고서 재생성 버튼 (메인 LLM 호출)
|
|
|
|
| 355 |
if st.session_state.last_report:
|
| 356 |
st.markdown("##### 🔄 다른 성향으로 보고서 다시 뽑기")
|
| 357 |
|
|
|
|
| 366 |
)
|
| 367 |
|
| 368 |
with col_regen:
|
| 369 |
+
if st.button(f"'{new_style}' 스타일로 재생성", use_container_width=True, "my_regen_button"):
|
| 370 |
with st.spinner(f"'{new_style}' 스타일로 보고서를 다시 작성 중입니다..."):
|
| 371 |
regenerated_reports = generate_llm_reports(
|
| 372 |
st.session_state.portfolio,
|
|
|
|
| 375 |
st.session_state.last_report = regenerated_reports
|
| 376 |
st.rerun() # 화면을 즉시 새로고침
|
| 377 |
|
|
|
|
| 378 |
# 7. 보고서 출력 섹션
|
|
|
|
| 379 |
st.divider()
|
| 380 |
|
| 381 |
if st.session_state.last_report:
|
|
|
|
| 391 |
else:
|
| 392 |
st.info("보고서를 생성하면 이 곳에 결과가 표시됩니다.")
|
| 393 |
|
|
|
|
| 394 |
# 8. (신규) LLM 모델 로딩 (모든 UI를 그린 후 마지막에 실행)
|
|
|
|
|
|
|
| 395 |
# peft_model, tokenizer를 st.session_state로 관리
|
| 396 |
if 'peft_model' not in st.session_state:
|
| 397 |
st.session_state.peft_model = None
|