nkust_CSA / app.py
jeff7522553
初始化
32adef4
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()