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()