Spaces:
Runtime error
Runtime error
| import logging | |
| import gradio as gr | |
| import pandas as pd | |
| import torch | |
| import numpy as np | |
| import matplotlib.pyplot as plt | |
| from GoogleNews import GoogleNews | |
| from transformers import pipeline | |
| from datetime import datetime, timedelta | |
| import matplotlib | |
| import yfinance as yf | |
| matplotlib.use('Agg') | |
| # Set up logging | |
| logging.basicConfig( | |
| level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" | |
| ) | |
| SENTIMENT_ANALYSIS_MODEL = ( | |
| "mrm8488/distilroberta-finetuned-financial-news-sentiment-analysis" | |
| ) | |
| DEVICE = "cuda" if torch.cuda.is_available() else "cpu" | |
| logging.info(f"Using device: {DEVICE}") | |
| logging.info("Initializing sentiment analysis model...") | |
| sentiment_analyzer = pipeline( | |
| "sentiment-analysis", model=SENTIMENT_ANALYSIS_MODEL, device=DEVICE | |
| ) | |
| logging.info("Model initialized successfully") | |
| # μμ₯ μ’ λͺ© μ¬λ³Ό λ§€νμ μν μΌλ°μ μΈ μ’ λͺ©λͺ μ¬μ (νμμ λ°λΌ νμ₯) | |
| COMMON_TICKERS = { | |
| "apple": "AAPL", | |
| "microsoft": "MSFT", | |
| "amazon": "AMZN", | |
| "google": "GOOGL", | |
| "alphabet": "GOOGL", | |
| "facebook": "META", | |
| "meta": "META", | |
| "tesla": "TSLA", | |
| "nvidia": "NVDA", | |
| "netflix": "NFLX", | |
| "amd": "AMD", | |
| "intel": "INTC", | |
| "ibm": "IBM", | |
| "oracle": "ORCL", | |
| "paypal": "PYPL", | |
| "adobe": "ADBE", | |
| "cisco": "CSCO", | |
| "bitcoin": "BTC-USD", | |
| "ethereum": "ETH-USD", | |
| "dogecoin": "DOGE-USD", | |
| "cardano": "ADA-USD", | |
| "xrp": "XRP-USD", | |
| "litecoin": "LTC-USD", | |
| "samsung": "005930.KS", # νκ΅ μΌμ±μ μ | |
| "hyundai": "005380.KS", # νλμλμ°¨ | |
| "sk hynix": "000660.KS", # SKνμ΄λμ€ | |
| "lg": "003550.KS", # LG | |
| "lge": "066570.KS", # LGμ μ | |
| "ncsoft": "036570.KS", # μμ¨μννΈ | |
| "kakao": "035720.KS", # μΉ΄μΉ΄μ€ | |
| "naver": "035420.KS", # λ€μ΄λ² | |
| "νλμ°¨": "005380.KS", # νλμλμ°¨ | |
| "μΌμ±μ μ": "005930.KS", # μΌμ±μ μ | |
| "μΌμ±": "005930.KS", # μΌμ±μ μ | |
| "μΉ΄μΉ΄μ€": "035720.KS", # μΉ΄μΉ΄μ€ | |
| "λ€μ΄λ²": "035420.KS", # λ€μ΄λ² | |
| } | |
| def fetch_articles(query, max_articles=30): | |
| try: | |
| logging.info(f"Fetching up to {max_articles} articles for query: '{query}'") | |
| googlenews = GoogleNews(lang="en") | |
| googlenews.search(query) | |
| # 첫 νμ΄μ§ κ²°κ³Ό κ°μ Έμ€κΈ° | |
| articles = googlenews.result() | |
| # λͺ©ν κΈ°μ¬ μμ λλ¬ν λκΉμ§ μΆκ° νμ΄μ§ κ°μ Έμ€κΈ° | |
| page = 2 | |
| while len(articles) < max_articles and page <= 10: # μ΅λ 10νμ΄μ§κΉμ§λ§ μλ | |
| logging.info(f"Fetched {len(articles)} articles so far. Getting page {page}...") | |
| googlenews.get_page(page) | |
| page_results = googlenews.result() | |
| # μ κ²°κ³Όκ° μμΌλ©΄ μ€λ¨ | |
| if not page_results: | |
| logging.info(f"No more results found after page {page-1}") | |
| break | |
| articles.extend(page_results) | |
| page += 1 | |
| # μ΅λ κΈ°μ¬ μλ‘ μ ν | |
| articles = articles[:max_articles] | |
| logging.info(f"Successfully fetched {len(articles)} articles") | |
| return articles | |
| except Exception as e: | |
| logging.error( | |
| f"Error while searching articles for query: '{query}'. Error: {e}" | |
| ) | |
| raise gr.Error( | |
| f"Unable to search articles for query: '{query}'. Try again later...", | |
| duration=5, | |
| ) | |
| def analyze_article_sentiment(article): | |
| logging.info(f"Analyzing sentiment for article: {article['title']}") | |
| sentiment = sentiment_analyzer(article["desc"])[0] | |
| article["sentiment"] = sentiment | |
| return article | |
| def calculate_time_weight(article_date_str): | |
| """ | |
| κΈ°μ¬ μκ° κΈ°μ€μΌλ‘ κ°μ€μΉ κ³μ° | |
| - 1μκ° λ΄ κΈ°μ¬λ 24% κ°μ€μΉ | |
| - μκ°μ΄ μ§λ μλ‘ 1%μ© κ°μ (μ΅μ 1%) | |
| - μ: 1μκ° λ΄ κΈ°μ¬ = 24%, 10μκ° μ κΈ°μ¬ = 15%, 24μκ° μ κΈ°μ¬ = 1% | |
| - 24μκ° μ΄μμ΄λ©΄ 1%λ‘ κ³ μ | |
| """ | |
| try: | |
| # κΈ°μ¬ λ μ§ λ¬Έμμ΄ νμ± (λ€μν νμ μ²λ¦¬) | |
| date_formats = [ | |
| '%a, %d %b %Y %H:%M:%S %z', # κΈ°λ³Έ GoogleNews νμ | |
| '%Y-%m-%d %H:%M:%S', | |
| '%a, %d %b %Y %H:%M:%S', | |
| '%Y-%m-%dT%H:%M:%S%z', | |
| '%a %b %d, %Y', | |
| '%d %b %Y' | |
| ] | |
| parsed_date = None | |
| for format_str in date_formats: | |
| try: | |
| parsed_date = datetime.strptime(article_date_str, format_str) | |
| break | |
| except ValueError: | |
| continue | |
| # μ΄λ€ νμμΌλ‘λ νμ±ν μ μμΌλ©΄ νμ¬ μκ° κΈ°μ€ 24μκ° μ μΌλ‘ κ°μ | |
| if parsed_date is None: | |
| logging.warning(f"Could not parse date: {article_date_str}, using default 24h ago") | |
| return 0.01 # μ΅μ κ°μ€μΉ 1% | |
| # νμ¬ μκ°κ³Όμ μ°¨μ΄ κ³μ° (μκ° λ¨μ) | |
| now = datetime.now() | |
| if parsed_date.tzinfo is not None: | |
| now = now.replace(tzinfo=parsed_date.tzinfo) | |
| hours_diff = (now - parsed_date).total_seconds() / 3600 | |
| # 24μκ° μ΄λ΄μΈ κ²½μ°λ§ κ³ λ € | |
| if hours_diff < 1: # 1μκ° μ΄λ΄ | |
| return 0.24 # 24% κ°μ€μΉ | |
| elif hours_diff < 24: # 1~23μκ° | |
| # 1μκ°λΉ 1%μ© κ°μ (1μκ° = 24%, 2μκ° = 23%, ...) | |
| return max(0.01, 0.24 - ((hours_diff - 1) * 0.01)) | |
| else: | |
| return 0.01 # 24μκ° μ΄μ μ§λ κΈ°μ¬λ 1% κ°μ€μΉ | |
| except Exception as e: | |
| logging.error(f"Error calculating time weight: {e}") | |
| return 0.01 # μ€λ₯ λ°μ μ μ΅μ κ°μ€μΉ μ μ© | |
| def calculate_sentiment_score(sentiment_label, time_weight): | |
| """ | |
| κ°μ± λ μ΄λΈμ λ°λ₯Έ κΈ°λ³Έ μ μ κ³μ° λ° μκ° κ°μ€μΉ μ μ© | |
| - positive: +3μ | |
| - neutral: 0μ | |
| - negative: -3μ | |
| μκ° κ°μ€μΉλ λ°±λΆμ¨λ‘ μ μ© (κΈ°λ³Έ μ μμ κ°μ€μΉ % λ§νΌ μΆκ°) | |
| μ: | |
| - 1μκ° λ΄ κΈμ κΈ°μ¬: 3μ + (3 * 24%) = 3 + 0.72 = 3.72μ | |
| - 10μκ° μ λΆμ κΈ°μ¬: -3μ + (-3 * 15%) = -3 - 0.45 = -3.45μ | |
| """ | |
| base_score = { | |
| 'positive': 3, | |
| 'neutral': 0, | |
| 'negative': -3 | |
| }.get(sentiment_label, 0) | |
| # κ°μ€μΉλ₯Ό μ μ©ν μΆκ° μ μ κ³μ° | |
| weighted_addition = base_score * time_weight | |
| return base_score, weighted_addition | |
| def get_stock_ticker(asset_name): | |
| """ | |
| μμ°λͺ μΌλ‘λΆν° μ£Όμ ν°μ»€ μ¬λ³Όμ μΆμΆ | |
| """ | |
| logging.info(f"Identifying ticker for: {asset_name}") | |
| # μλ¬Έμλ‘ λ³ννμ¬ λ§€ν νμΈ | |
| asset_lower = asset_name.lower().strip() | |
| # μ§μ ν°μ»€λ‘ μ λ ₯ν κ²½μ° (λλ¬Έμ 3-5μ νν) | |
| if asset_name.isupper() and 2 <= len(asset_name) <= 6: | |
| logging.info(f"Input appears to be a ticker symbol: {asset_name}") | |
| return asset_name | |
| # μΌλ°μ μΈ μ’ λͺ©λͺ λ§€ν νμΈ | |
| if asset_lower in COMMON_TICKERS: | |
| ticker = COMMON_TICKERS[asset_lower] | |
| logging.info(f"Found ticker in common tickers map: {ticker}") | |
| return ticker | |
| # μ¬λ¬ λ¨μ΄λ‘ λ μ΄λ¦μ κ° λΆλΆμ λν κ²μλ μλ | |
| asset_parts = asset_lower.split() | |
| for part in asset_parts: | |
| if part in COMMON_TICKERS: | |
| ticker = COMMON_TICKERS[part] | |
| logging.info(f"Found ticker for part '{part}': {ticker}") | |
| return ticker | |
| # κ·Έ μΈμ κ²½μ° μ§μ ν°μ»€λ‘ μλ | |
| potential_ticker = asset_name.upper().replace(" ", "") | |
| if 2 <= len(potential_ticker) <= 6: | |
| # μ€μ λ‘ μ‘΄μ¬νλμ§ νμΈ | |
| try: | |
| logging.info(f"Trying potential ticker: {potential_ticker}") | |
| test_data = yf.download(potential_ticker, period="1d", progress=False) | |
| if not test_data.empty: | |
| logging.info(f"Valid ticker found: {potential_ticker}") | |
| return potential_ticker | |
| except Exception as e: | |
| logging.debug(f"Error testing potential ticker: {e}") | |
| # κ·Έ μΈμ κ²½μ° yfinanceλ‘ κ²μ μλ (info λ°μ΄ν°) | |
| try: | |
| # μΌλΆ ν°μ»€λ μ§μ yfinance κΈ°λ° κ²μμΌλ‘λ μ€λ₯κ° λ°μν μ μμ | |
| ticker_search = yf.Ticker(asset_name) | |
| try: | |
| info = ticker_search.info | |
| if 'symbol' in info and info['symbol']: | |
| ticker = info['symbol'] | |
| logging.info(f"Found ticker from info API: {ticker}") | |
| return ticker | |
| except (ValueError, KeyError, TypeError) as e: | |
| logging.debug(f"Error getting ticker info: {e}") | |
| pass | |
| except Exception as e: | |
| logging.debug(f"Error initializing ticker object: {e}") | |
| # μΆκ° μλ: μΌλ°μ μΈ λ―Έκ΅ μ¦μ ν°μ»€ νμ νμΈ | |
| major_exchanges = ["", ".KS", ".KQ", "-USD"] # μ£Όμ κ±°λμ μ λ―Έμ¬ (νκ΅ ν¬ν¨) | |
| for exchange in major_exchanges: | |
| try: | |
| test_ticker = f"{asset_name.upper().replace(' ', '')}{exchange}" | |
| logging.info(f"Trying with exchange suffix: {test_ticker}") | |
| test_data = yf.download(test_ticker, period="1d", progress=False) | |
| if not test_data.empty: | |
| logging.info(f"Valid ticker found with suffix: {test_ticker}") | |
| return test_ticker | |
| except: | |
| pass | |
| logging.warning(f"Could not identify ticker for: {asset_name}") | |
| return None | |
| def create_stock_chart(ticker, period="1mo"): | |
| """ | |
| μ£Όμ ν°μ»€μ λν μ°¨νΈ μμ± | |
| """ | |
| try: | |
| logging.info(f"Fetching stock data for {ticker}") | |
| # Graceful handling for problematic symbols | |
| try: | |
| stock_data = yf.download(ticker, period=period, progress=False) | |
| except Exception as dl_error: | |
| logging.error(f"Error downloading stock data: {dl_error}") | |
| # Try alternative symbol format | |
| if "-" in ticker: | |
| alt_ticker = ticker.replace("-", ".") | |
| logging.info(f"Trying alternative ticker format: {alt_ticker}") | |
| stock_data = yf.download(alt_ticker, period=period, progress=False) | |
| else: | |
| raise dl_error | |
| if len(stock_data) == 0: | |
| logging.warning(f"No stock data found for ticker: {ticker}") | |
| return None | |
| # λ°μ΄ν° νμΈ λ° λλ²κ·Έ λ‘κΉ | |
| logging.info(f"Downloaded data shape: {stock_data.shape}") | |
| logging.info(f"Data columns: {stock_data.columns.tolist()}") | |
| # κ·Έλν μμ± | |
| fig, ax = plt.subplots(figsize=(10, 6)) | |
| # μ’ κ° κ·Έλν - λ©ν°μΈλ±μ€ μ²λ¦¬ | |
| if isinstance(stock_data.columns, pd.MultiIndex): | |
| # λ©ν°μΈλ±μ€μΈ κ²½μ° ('Close', ticker) νν | |
| close_col = ('Close', ticker) | |
| if close_col in stock_data.columns: | |
| ax.plot(stock_data.index, stock_data[close_col], label='Close Price', color='blue') | |
| # μ΄λνκ· μ μΆκ° (20μΌ) | |
| if len(stock_data) > 20: | |
| stock_data['MA20'] = stock_data[close_col].rolling(window=20).mean() | |
| ax.plot(stock_data.index, stock_data['MA20'], label='20-day MA', color='orange') | |
| # κ±°λλ μλΈνλ‘― μΆκ° (κ±°λλμ΄ μλ κ²½μ°λ§) | |
| volume_col = ('Volume', ticker) | |
| if volume_col in stock_data.columns and not stock_data[volume_col].isna().all(): | |
| ax2 = ax.twinx() | |
| ax2.bar(stock_data.index, stock_data[volume_col], alpha=0.3, color='gray', label='Volume') | |
| ax2.set_ylabel('Volume') | |
| # λ²λ‘ μΆκ° (κ±°λλ μλ κ²½μ°) | |
| lines, labels = ax.get_legend_handles_labels() | |
| lines2, labels2 = ax2.get_legend_handles_labels() | |
| ax.legend(lines + lines2, labels + labels2, loc='upper left') | |
| else: | |
| # κ±°λλ μλ κ²½μ° μ’ κ°λ§ νμ | |
| ax.legend(loc='upper left') | |
| else: | |
| raise ValueError(f"Close column not found in data columns: {stock_data.columns}") | |
| else: | |
| # μΌλ° μΈλ±μ€μΈ κ²½μ° | |
| if 'Close' in stock_data.columns: | |
| ax.plot(stock_data.index, stock_data['Close'], label='Close Price', color='blue') | |
| # μ΄λνκ· μ μΆκ° (20μΌ) | |
| if len(stock_data) > 20: | |
| stock_data['MA20'] = stock_data['Close'].rolling(window=20).mean() | |
| ax.plot(stock_data.index, stock_data['MA20'], label='20-day MA', color='orange') | |
| # κ±°λλ μλΈνλ‘― μΆκ° (κ±°λλμ΄ μλ κ²½μ°λ§) | |
| if 'Volume' in stock_data.columns and not stock_data['Volume'].isna().all(): | |
| ax2 = ax.twinx() | |
| ax2.bar(stock_data.index, stock_data['Volume'], alpha=0.3, color='gray', label='Volume') | |
| ax2.set_ylabel('Volume') | |
| # λ²λ‘ μΆκ° (κ±°λλ μλ κ²½μ°) | |
| lines, labels = ax.get_legend_handles_labels() | |
| lines2, labels2 = ax2.get_legend_handles_labels() | |
| ax.legend(lines + lines2, labels + labels2, loc='upper left') | |
| else: | |
| # κ±°λλ μλ κ²½μ° μ’ κ°λ§ νμ | |
| ax.legend(loc='upper left') | |
| else: | |
| raise ValueError(f"Close column not found in data columns: {stock_data.columns}") | |
| # μ°¨νΈ μ€νμΌλ§ | |
| ax.set_title(f"{ticker} Stock Price") | |
| ax.set_xlabel('Date') | |
| ax.set_ylabel('Price') | |
| ax.grid(True, alpha=0.3) | |
| plt.tight_layout() | |
| # μ΄λ―Έμ§ μ μ₯ | |
| chart_path = f"stock_chart_{ticker.replace('-', '_').replace('.', '_')}.png" | |
| plt.savefig(chart_path) | |
| plt.close() | |
| logging.info(f"Stock chart created: {chart_path}") | |
| return chart_path | |
| except Exception as e: | |
| logging.error(f"Error creating stock chart for {ticker}: {e}") | |
| # μ€λ₯ λ°μ μμλ κ·Έλν μμ± μλ (κΈ°λ³Έ ν μ€νΈ μλ΄) | |
| try: | |
| fig, ax = plt.subplots(figsize=(10, 6)) | |
| ax.text(0.5, 0.5, f"Unable to load data for {ticker}\nError: {str(e)}", | |
| horizontalalignment='center', verticalalignment='center', transform=ax.transAxes) | |
| ax.set_axis_off() | |
| chart_path = f"stock_chart_error_{ticker.replace('-', '_').replace('.', '_')}.png" | |
| plt.savefig(chart_path) | |
| plt.close() | |
| return chart_path | |
| except: | |
| return None | |
| def analyze_asset_sentiment(asset_name): | |
| logging.info(f"Starting sentiment analysis for asset: {asset_name}") | |
| logging.info("Fetching up to 30 articles") | |
| articles = fetch_articles(asset_name, max_articles=30) | |
| logging.info("Analyzing sentiment of each article") | |
| analyzed_articles = [analyze_article_sentiment(article) for article in articles] | |
| # κ° κΈ°μ¬μ λν μκ° κ°μ€μΉ λ° κ°μ± μ μ κ³μ° | |
| for article in analyzed_articles: | |
| time_weight = calculate_time_weight(article["date"]) | |
| article["time_weight"] = time_weight | |
| sentiment_label = article["sentiment"]["label"] | |
| base_score, weighted_addition = calculate_sentiment_score(sentiment_label, time_weight) | |
| article["base_score"] = base_score | |
| article["weighted_addition"] = weighted_addition | |
| article["total_score"] = base_score + weighted_addition | |
| logging.info("Sentiment analysis completed") | |
| # μ’ ν© μ μ κ³μ° λ° κ·Έλν μμ± | |
| sentiment_summary = create_sentiment_summary(analyzed_articles, asset_name) | |
| # μ£Όμ ν°μ»€ νμΈ λ° μ°¨νΈ μμ± | |
| stock_chart = None | |
| ticker = get_stock_ticker(asset_name) | |
| if ticker: | |
| logging.info(f"Found ticker {ticker} for asset {asset_name}") | |
| stock_chart = create_stock_chart(ticker) | |
| return convert_to_dataframe(analyzed_articles), sentiment_summary, stock_chart, ticker | |
| def create_sentiment_summary(analyzed_articles, asset_name): | |
| """ | |
| κ°μ± λΆμ κ²°κ³Όλ₯Ό μμ½νκ³ κ·Έλνλ‘ μκ°ν | |
| """ | |
| total_articles = len(analyzed_articles) | |
| positive_count = sum(1 for a in analyzed_articles if a["sentiment"]["label"] == "positive") | |
| neutral_count = sum(1 for a in analyzed_articles if a["sentiment"]["label"] == "neutral") | |
| negative_count = sum(1 for a in analyzed_articles if a["sentiment"]["label"] == "negative") | |
| # κΈ°λ³Έ μ μ ν©κ³ | |
| base_score_sum = sum(a["base_score"] for a in analyzed_articles) | |
| # κ°μ€μΉ μ μ© μ μ ν©κ³ | |
| weighted_score_sum = sum(a["total_score"] for a in analyzed_articles) | |
| # κ·Έλν μμ± | |
| fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6)) | |
| # 1. κ°μ± λΆν¬ νμ΄ μ°¨νΈ | |
| labels = ['Positive', 'Neutral', 'Negative'] | |
| sizes = [positive_count, neutral_count, negative_count] | |
| colors = ['green', 'gray', 'red'] | |
| ax1.pie(sizes, labels=labels, colors=colors, autopct='%1.1f%%', startangle=90) | |
| ax1.axis('equal') | |
| ax1.set_title(f'Sentiment Distribution for {asset_name}') | |
| # 2. μκ°λ³ κ°μ€μΉ μ μ© μ μ (μ λ ¬) | |
| sorted_articles = sorted(analyzed_articles, key=lambda x: x.get("date", ""), reverse=True) | |
| # μ΅λ νμν κΈ°μ¬ μ (κ°λ μ±μ μν΄) | |
| max_display = min(15, len(sorted_articles)) | |
| display_articles = sorted_articles[:max_display] | |
| dates = [a.get("date", "")[:10] for a in display_articles] # λ μ§ λΆλΆλ§ νμ | |
| scores = [a.get("total_score", 0) for a in display_articles] | |
| # μ μμ λ°λ₯Έ μμ μ€μ | |
| bar_colors = ['green' if s > 0 else 'red' if s < 0 else 'gray' for s in scores] | |
| bars = ax2.bar(range(len(dates)), scores, color=bar_colors) | |
| ax2.set_xticks(range(len(dates))) | |
| ax2.set_xticklabels(dates, rotation=45, ha='right') | |
| ax2.set_ylabel('Weighted Sentiment Score') | |
| ax2.set_title(f'Recent Article Scores for {asset_name}') | |
| ax2.axhline(y=0, color='black', linestyle='-', alpha=0.3) | |
| # μμ½ ν μ€νΈ μΆκ° | |
| summary_text = f""" | |
| Analysis Summary for {asset_name}: | |
| Total Articles: {total_articles} | |
| Positive: {positive_count} ({positive_count/total_articles*100:.1f}%) | |
| Neutral: {neutral_count} ({neutral_count/total_articles*100:.1f}%) | |
| Negative: {negative_count} ({negative_count/total_articles*100:.1f}%) | |
| Base Score Sum: {base_score_sum:.2f} | |
| Weighted Score Sum: {weighted_score_sum:.2f} | |
| """ | |
| plt.figtext(0.5, 0.01, summary_text, ha='center', fontsize=10, bbox={"facecolor":"orange", "alpha":0.2, "pad":5}) | |
| plt.tight_layout(rect=[0, 0.1, 1, 0.95]) | |
| # μ΄λ―Έμ§ μ μ₯ | |
| fig_path = f"sentiment_summary_{asset_name.replace(' ', '_')}.png" | |
| plt.savefig(fig_path) | |
| plt.close() | |
| return fig_path | |
| def convert_to_dataframe(analyzed_articles): | |
| df = pd.DataFrame(analyzed_articles) | |
| df["Title"] = df.apply( | |
| lambda row: f'<a href="{row["link"]}" target="_blank">{row["title"]}</a>', | |
| axis=1, | |
| ) | |
| df["Description"] = df["desc"] | |
| df["Date"] = df["date"] | |
| def sentiment_badge(sentiment): | |
| colors = { | |
| "negative": "red", | |
| "neutral": "gray", | |
| "positive": "green", | |
| } | |
| color = colors.get(sentiment, "grey") | |
| return f'<span style="background-color: {color}; color: white; padding: 2px 6px; border-radius: 4px;">{sentiment}</span>' | |
| df["Sentiment"] = df["sentiment"].apply(lambda x: sentiment_badge(x["label"])) | |
| # μ μ μ»¬λΌ μΆκ° | |
| df["Base Score"] = df["base_score"] | |
| df["Weight"] = df["time_weight"].apply(lambda x: f"{x*100:.0f}%") | |
| df["Total Score"] = df["total_score"].apply(lambda x: f"{x:.2f}") | |
| return df[["Sentiment", "Title", "Description", "Date", "Base Score", "Weight", "Total Score"]] | |
| def main(): | |
| with gr.Blocks() as iface: | |
| gr.Markdown("# Trading Asset Sentiment Analysis") | |
| gr.Markdown( | |
| "Enter the name of a trading asset, and I'll fetch recent articles and analyze their sentiment!" | |
| ) | |
| with gr.Row(): | |
| input_asset = gr.Textbox( | |
| label="Asset Name", | |
| lines=1, | |
| placeholder="Enter the name of the trading asset...", | |
| ) | |
| with gr.Row(): | |
| analyze_button = gr.Button("Analyze Sentiment", size="sm") | |
| # μμ μ λ ₯κ°μ μ½λμ μ μλ ν°μ»€ λ§€νμ ν€λ€λ‘ λ°μ (μ€λ³΅λμ§ μλλ‘ μ λ ¬) | |
| examples_list = sorted(set(COMMON_TICKERS.keys()), key=lambda x: x.lower()) | |
| gr.Examples( | |
| examples=examples_list, | |
| inputs=input_asset, | |
| ) | |
| # μ£Όμ μ°¨νΈ μμ μΆκ° | |
| with gr.Row(): | |
| with gr.Column(): | |
| with gr.Blocks(): | |
| gr.Markdown("## Stock Chart") | |
| with gr.Row(): | |
| stock_chart = gr.Image(type="filepath", label="Stock Price Chart") | |
| ticker_info = gr.Textbox(label="Ticker Symbol") | |
| with gr.Row(): | |
| with gr.Column(): | |
| with gr.Blocks(): | |
| gr.Markdown("## Sentiment Summary") | |
| sentiment_summary = gr.Image(type="filepath", label="Sentiment Analysis Summary") | |
| with gr.Row(): | |
| with gr.Column(): | |
| with gr.Blocks(): | |
| gr.Markdown("## Articles and Sentiment Analysis") | |
| articles_output = gr.Dataframe( | |
| headers=["Sentiment", "Title", "Description", "Date", "Base Score", "Weight", "Total Score"], | |
| datatype=["markdown", "html", "markdown", "markdown", "number", "markdown", "markdown"], | |
| wrap=False, | |
| ) | |
| analyze_button.click( | |
| analyze_asset_sentiment, | |
| inputs=[input_asset], | |
| outputs=[articles_output, sentiment_summary, stock_chart, ticker_info], | |
| ) | |
| logging.info("Launching Gradio interface") | |
| iface.queue().launch() | |
| if __name__ == "__main__": | |
| main() | |