StockPulse / app.py
llllllllllllllllllllllllllleeeeeeeeeeeeee's picture
Update app.py
e9fe77c verified
raw
history blame
9.26 kB
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()