|
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 |
|
|
|
|
|
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 |
|
|
|
|
|
API_KEY = "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" |
|
|
|
|
|
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) |
|
|
|
|
|
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=[]) |
|
|
|
|
|
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_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] |
|
) |
|
|
|
|
|
demo.launch() |
|
|