Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import pandas as pd | |
| import plotly.graph_objects as go | |
| import random | |
| from datetime import date, timedelta | |
| # --- 1. 模擬資料生成 --- | |
| def generate_price_data(stock_id: str) -> pd.DataFrame: | |
| """生成模擬的每日股價資料 (開、高、低、收)""" | |
| today = date.today() | |
| dates = [today - timedelta(days=i) for i in range(90)][::-1] | |
| prices = [] | |
| random.seed(hash(stock_id)) | |
| current_price = random.uniform(50, 800) | |
| for d in dates: | |
| open_price = current_price * random.uniform(0.98, 1.02) | |
| high_price = open_price * random.uniform(1.0, 1.03) | |
| low_price = open_price * random.uniform(0.97, 1.0) | |
| close_price = random.uniform(low_price, high_price) | |
| prices.append({ | |
| "Date": d, "Open": round(open_price, 2), "High": round(high_price, 2), | |
| "Low": round(low_price, 2), "Close": round(close_price, 2), | |
| "Volume": random.randint(1_000_000, 50_000_000) | |
| }) | |
| current_price = close_price | |
| return pd.DataFrame(prices) | |
| def generate_search_volume_data(stock_id: str) -> pd.DataFrame: | |
| """生成模擬的每日搜尋聲量""" | |
| today = date.today() | |
| dates = [today - timedelta(days=i) for i in range(90)][::-1] | |
| volumes = [] | |
| random.seed(hash(stock_id) + 1) | |
| base_volume = random.randint(20, 60) | |
| for d in dates: | |
| spike = random.uniform(15, 40) if random.random() > 0.92 else 0 | |
| volume = base_volume + random.uniform(-5, 5) + spike | |
| volumes.append({"Date": d, "Search Volume": max(10, round(volume))}) | |
| return pd.DataFrame(volumes) | |
| def generate_financial_data(stock_id: str) -> pd.DataFrame: | |
| """生成模擬的季度財務報表""" | |
| random.seed(hash(stock_id)) | |
| data = { | |
| "項目": ["營業收入 (百萬)", "營業毛利 (百萬)", "稅後淨利 (百萬)", "每股盈餘 (EPS)"], | |
| "2024 Q3": [random.randint(8000, 12000), random.randint(3000, 5000), random.randint(1500, 3000), round(random.uniform(1, 5), 2)], | |
| "2024 Q2": [random.randint(7500, 11000), random.randint(2800, 4800), random.randint(1400, 2800), round(random.uniform(0.8, 4.5), 2)], | |
| "2024 Q1": [random.randint(7000, 10000), random.randint(2500, 4500), random.randint(1200, 2500), round(random.uniform(0.7, 4.0), 2)], | |
| } | |
| return pd.DataFrame(data) | |
| def generate_institutional_trades(stock_id: str) -> pd.DataFrame: | |
| """生成模擬的法人買賣資料""" | |
| today = date.today() | |
| institutions = ["外資", "投信", "自營商"] | |
| actions = ["買超", "賣超"] | |
| records = [] | |
| for i in range(15): | |
| records.append({ | |
| "日期": today - timedelta(days=i), "法人機構": random.choice(institutions), | |
| "買賣超": random.choice(actions), "張數": random.randint(100, 5000) | |
| }) | |
| return pd.DataFrame(records) | |
| def generate_recent_news(stock_id: str) -> list: | |
| """生成模擬的近期新聞,包含標題和內容""" | |
| random.seed(hash(stock_id)) | |
| headlines = [ | |
| { | |
| "headline": f"【產業動態】分析師看好 {stock_id} 第四季度的市場表現", | |
| "content": f"根據最新的市場分析報告,由於全球供應鏈問題緩解以及終端需求回溫,分析師普遍預期 {stock_id} 在第四季度的營收將季增15%,達到歷史新高。報告中特別指出,該公司在新興市場的佈局已見成效,有望成為下一波成長的主要動能。" | |
| }, | |
| { | |
| "headline": f"【公司新聞】{stock_id} 宣布將於下月發布新一代 AI 晶片", | |
| "content": f"{stock_id} 今日向媒體發出邀請函,確認將於下月15日舉行年度產品發表會。市場預期,會中的最大亮點將是代號為 'Phoenix' 的新一代 AI 運算晶片。據傳該晶片的效能較前代提升了70%,並大幅降低功耗,有望鞏固其在雲端運算市場的領導地位。" | |
| }, | |
| { | |
| "headline": f"【法人觀點】某外資機構上調 {stock_id} 評級至「強力買入」", | |
| "content": f"知名外資機構今日發布研究報告,將 {stock_id} 的股票評級從「中立」一口氣上調至「強力買入」,目標價也提高了30%。報告認為,市場低估了 {stock_id} 在汽車電子領域的潛力,預計相關業務將在未來三年內貢獻超過20%的總營收。" | |
| } | |
| ] | |
| random.shuffle(headlines) | |
| return headlines | |
| # --- 2. 主要處理函式 --- | |
| def get_stock_info(stock_id: str): | |
| """根據股票代號獲取所有資訊並生成圖表和資料""" | |
| if not stock_id: | |
| empty_fig = go.Figure() | |
| empty_df = pd.DataFrame() | |
| # 返回與輸出元件數量相符的空更新 | |
| return ( | |
| empty_fig, empty_fig, empty_df, empty_df, | |
| gr.update(visible=False), gr.update(value=""), | |
| gr.update(visible=False), gr.update(value=""), | |
| gr.update(visible=False), gr.update(value="") | |
| ) | |
| # 生成資料 | |
| price_df = generate_price_data(stock_id) | |
| search_volume_df = generate_search_volume_data(stock_id) | |
| financial_df = generate_financial_data(stock_id) | |
| trades_df = generate_institutional_trades(stock_id) | |
| news_list = generate_recent_news(stock_id) | |
| # 創建 K線圖 | |
| candlestick_fig = go.Figure(data=[go.Candlestick( | |
| x=price_df['Date'], open=price_df['Open'], high=price_df['High'], | |
| low=price_df['Low'], close=price_df['Close'], name='K線' | |
| )]) | |
| candlestick_fig.update_layout( | |
| xaxis_title='日期', yaxis_title='價格', xaxis_rangeslider_visible=False, | |
| template="plotly_dark", margin=dict(l=40, r=40, t=40, b=40) | |
| ) | |
| # 創建搜尋聲量折線圖 | |
| search_volume_fig = go.Figure(data=[go.Scatter( | |
| x=search_volume_df['Date'], y=search_volume_df['Search Volume'], | |
| mode='lines', name='聲量', line=dict(color='cyan') | |
| )]) | |
| search_volume_fig.update_layout( | |
| xaxis_title='日期', yaxis_title='相對搜尋熱度', template="plotly_dark", | |
| margin=dict(l=40, r=40, t=40, b=40) | |
| ) | |
| # 返回所有元件的更新值 | |
| return ( | |
| candlestick_fig, search_volume_fig, financial_df, trades_df, | |
| gr.update(label=news_list[0]['headline'], visible=True), | |
| gr.update(value=news_list[0]['content']), | |
| gr.update(label=news_list[1]['headline'], visible=True), | |
| gr.update(value=news_list[1]['content']), | |
| gr.update(label=news_list[2]['headline'], visible=True), | |
| gr.update(value=news_list[2]['content']) | |
| ) | |
| # --- 3. Gradio 介面 --- | |
| with gr.Blocks(theme=gr.themes.Soft(), title="股票資訊查詢系統") as demo: | |
| gr.Markdown("# 📈 股票資訊查詢系統") | |
| gr.Markdown("輸入股票代號 (例如: 2330.TW, AAPL),然後按 Enter 或點擊送出按鈕來查詢相關資訊。") | |
| with gr.Row(): | |
| stock_input = gr.Textbox(label="股票代號", placeholder="例如: 2330.TW 或 AAPL", scale=4) | |
| submit_btn = gr.Button("送出查詢", scale=1) | |
| gr.Markdown("---") | |
| gr.Markdown("### 📊 股價 K線圖") | |
| price_plot = gr.Plot() | |
| gr.Markdown("### 📈 網路搜尋聲量") | |
| search_volume_plot = gr.Plot() | |
| gr.Markdown("### 📄 財務報表") | |
| financial_output = gr.Dataframe(interactive=False, wrap=True) | |
| gr.Markdown("### 🏢 法人買賣") | |
| trades_output = gr.Dataframe(interactive=False, wrap=True) | |
| gr.Markdown("### 📰 近期新聞") | |
| with gr.Column(): | |
| news_accordion_1 = gr.Accordion("新聞標題 1", visible=False) | |
| with news_accordion_1: | |
| news_content_1 = gr.Markdown() | |
| news_accordion_2 = gr.Accordion("新聞標題 2", visible=False) | |
| with news_accordion_2: | |
| news_content_2 = gr.Markdown() | |
| news_accordion_3 = gr.Accordion("新聞標題 3", visible=False) | |
| with news_accordion_3: | |
| news_content_3 = gr.Markdown() | |
| outputs_list = [ | |
| price_plot, search_volume_plot, financial_output, trades_output, | |
| news_accordion_1, news_content_1, | |
| news_accordion_2, news_content_2, | |
| news_accordion_3, news_content_3 | |
| ] | |
| submit_btn.click(fn=get_stock_info, inputs=stock_input, outputs=outputs_list) | |
| stock_input.submit(fn=get_stock_info, inputs=stock_input, outputs=outputs_list) | |
| if __name__ == "__main__": | |
| demo.launch() | |