import subprocess # 필요한 라이브러리 설치 및 업데이트 subprocess.run(["pip", "install", "--upgrade", "pip"]) subprocess.run(["pip", "install", "--upgrade", "openai", "yfinance", "gradio", "matplotlib", "Pillow"]) import yfinance as yf import os import matplotlib.font_manager as fm import matplotlib.pyplot as plt import numpy as np import re # 한글, 숫자, 기호를 남기기 위한 정규 표현식에 사용 import gradio as gr import io from PIL import Image from datetime import datetime, timedelta from openai import OpenAI # 1. 나눔고딕 폰트 설치 및 적용 def install_nanum_font(): try: subprocess.run(["apt-get", "update"], check=True) subprocess.run(["apt-get", "install", "-y", "fonts-nanum"], check=True) subprocess.run(["fc-cache", "-fv"], check=True) except Exception as e: print(f"폰트 설치 중 오류가 발생했습니다: {e}") install_nanum_font() # 나눔고딕 폰트 경로 설정 및 강제 적용 font_path = '/usr/share/fonts/truetype/nanum/NanumGothic.ttf' if os.path.exists(font_path): fm.fontManager.addfont(font_path) else: print("폰트를 찾을 수 없습니다.") # 나눔고딕 폰트 강제 적용 font_prop = fm.FontProperties(fname=font_path) plt.rcParams['font.family'] = font_prop.get_name() plt.rcParams['axes.unicode_minus'] = False # 마이너스 부호 깨짐 방지 # Perplexity AI API 설정 API_KEY = "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" # 여기에 Perplexity AI API 키를 입력하세요. # 뉴스 요약을 가져오는 함수 (기존 코드 유지) def get_real_news_summary(company, date): client = OpenAI(api_key=API_KEY, base_url="https://api.perplexity.ai") # 날짜 형식 조정 target_date = datetime.strptime(date, '%Y-%m-%d') start_date = (target_date - timedelta(days=1)).strftime('%Y-%m-%d') end_date = (target_date + timedelta(days=1)).strftime('%Y-%m-%d') messages = [ {"role": "system", "content": "You are a helpful assistant that summarizes stock news strictly in Korean."}, {"role": "user", "content": f"Summarize the stock news for {company} between {start_date} and {end_date} in Korean. Only focus on news within this date range."} ] try: response = client.chat.completions.create( model="llama-3.1-sonar-large-128k-online", messages=messages ) summary = response.choices[0].message.content # 한글, 숫자, 공백, 특수 기호만 남기는 정규 표현식 korean_only_summary = re.sub(r'[^\w\s#.,!%()\-\[\]]', '', summary) formatted_summary = re.sub(r'##\s*(.+)', r'**\1**', korean_only_summary) return formatted_summary except Exception as e: return f"뉴스 요약 중 에러가 발생했습니다: {str(e)}" def handle_click(company_name, date_clicked): return get_real_news_summary(company_name, date_clicked) # Gradio에서 사용할 함수 (뉴스 요약 포함) def update_news(input_value, selected_date): if selected_date == "" or selected_date is None: return "날짜를 선택해주세요." else: ticker = get_dynamic_ticker(input_value) company_name = ticker_to_name.get(ticker, input_value) return handle_click(company_name, selected_date) # 종목 동적 매칭 함수 def get_dynamic_ticker(input_value): if input_value == "나스닥 시총 1위": return get_top_market_cap_stock("나스닥") elif input_value == "나스닥 바이오텍 시총 1위": return get_top_market_cap_stock("나스닥", industry="Biotechnology") elif input_value == "나스닥 헬스케어 시총 1위": return get_top_market_cap_stock("나스닥", sector="Healthcare") elif input_value == "코스피 시총 1위": return get_top_market_cap_stock("코스피") elif input_value == "코스닥 시총 1위": return get_top_market_cap_stock("코스닥") else: return name_to_ticker.get(input_value, input_value) # 시가총액 상위 종목 가져오기 def get_top_market_cap_stock(market, sector=None, industry=None): if market == "나스닥": etf_ticker = "QQQ" elif market == "코스피": etf_ticker = "EWY" elif market == "코스닥": tickers = ["035420.KQ", "068270.KQ", "035720.KQ"] else: return None if market in ["나스닥", "코스피"]: etf = yf.Ticker(etf_ticker) holdings = etf.info.get('holdings', []) tickers = [holding['symbol'] for holding in holdings] largest_market_cap = 0 top_ticker = None for ticker in tickers: stock = yf.Ticker(ticker) stock_info = stock.info market_cap = stock_info.get('marketCap', 0) stock_sector = stock_info.get('sector', None) stock_industry = stock_info.get('industry', None) if sector and stock_sector != sector: continue if industry and stock_industry != industry: continue if market_cap > largest_market_cap: largest_market_cap = market_cap top_ticker = ticker ticker_to_name[ticker] = stock_info.get('shortName', ticker) return top_ticker # 주가 데이터를 가져오고 조건에 맞는 날짜와 그래프를 반환하는 함수 def display_stock_with_highlight(input_value, change_type, percent_change): try: ticker = get_dynamic_ticker(input_value) stock = yf.Ticker(ticker) stock_data = stock.history(period="5y") if stock_data.empty: return "주가 데이터를 찾을 수 없습니다.", [] stock_data['Change'] = stock_data['Close'].pct_change() * 100 percent_change = float(percent_change) if change_type == "상승": highlight_data = stock_data[stock_data['Change'] >= percent_change] color = "darkorange" elif change_type == "하락": highlight_data = stock_data[stock_data['Change'] <= -percent_change] color = "purple" else: return "Invalid change type", [] dates = stock_data.index.to_numpy() closing_prices = stock_data['Close'].to_numpy() plt.figure(figsize=(10, 6)) plt.plot(dates, closing_prices, color='gray', label=input_value) plt.scatter(highlight_data.index, highlight_data['Close'], color=color, label=f'{change_type} 포인트') for index, row in highlight_data.iterrows(): plt.text(index, row['Close'], index.strftime('%Y-%m-%d'), fontsize=10, fontweight='bold', color=color, ha='right') plt.axvline(x=index, color=color, linestyle='--', linewidth=1) company_name = ticker_to_name.get(ticker, input_value) plt.title(f'{company_name} 주가 추이', fontproperties=font_prop) plt.xlabel('날짜', fontproperties=font_prop) plt.ylabel('종가', fontproperties=font_prop) plt.legend() buf = io.BytesIO() plt.savefig(buf, format='png') plt.close() buf.seek(0) img = Image.open(buf) highlight_dates = highlight_data.index.strftime('%Y-%m-%d').tolist() return img, gr.update(choices=highlight_dates) except Exception as e: return f"Error processing data: {e}", gr.update(choices=[]) # Gradio 인터페이스 생성 (3열 레이아웃) with gr.Blocks() as demo: gr.Markdown("## 주가 그래프와 뉴스 요약") with gr.Row(): with gr.Column(): # 입력값을 담을 첫 번째 열 input_value = gr.Textbox(label="종목명 또는 티커 입력", placeholder="예: SK바이오팜, AAPL") change_type = gr.Dropdown(choices=["상승", "하락"], label="상승 또는 하락 선택", value="상승") percent_change = gr.Textbox(label="변동 퍼센트 (%)", placeholder="예: 10", value="10") submit_btn = gr.Button("Submit") # 예제 (동적으로 매칭) examples = [["SK바이오팜"], ["나스닥 시총 1위"], ["나스닥 바이오텍 시총 1위"], ["나스닥 헬스케어 시총 1위"], ["코스피 시총 1위"], ["코스닥 시총 1위"]] gr.Examples(examples=examples, inputs=[input_value]) with gr.Column(): # 그래프를 출력할 두 번째 열 plot = gr.Image(label="주가 그래프") date_dropdown = gr.Dropdown(label="조건에 해당하는 날짜 선택", choices=[]) with gr.Column(): # 뉴스 요약을 출력할 세 번째 열 news_output = gr.Markdown(label="뉴스 요약", value="") # 빈 칸으로 기본 표시 # Submit 버튼 클릭 시 그래프 및 날짜 드롭다운 업데이트 submit_btn.click( fn=display_stock_with_highlight, inputs=[input_value, change_type, percent_change], outputs=[plot, date_dropdown] ) # 날짜 선택 시 뉴스 요약 업데이트 date_dropdown.change( fn=update_news, inputs=[input_value, date_dropdown], outputs=[news_output] ) # Gradio 실행 demo.launch()