Spaces:
Sleeping
Sleeping
jeff7522553
commited on
Commit
·
32adef4
1
Parent(s):
0dee4d1
初始化
Browse files
app.py
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
import pandas as pd
|
| 3 |
+
import plotly.graph_objects as go
|
| 4 |
+
import random
|
| 5 |
+
from datetime import date, timedelta
|
| 6 |
+
|
| 7 |
+
# --- 1. 模擬資料生成 ---
|
| 8 |
+
|
| 9 |
+
def generate_price_data(stock_id: str) -> pd.DataFrame:
|
| 10 |
+
"""生成模擬的每日股價資料 (開、高、低、收)"""
|
| 11 |
+
today = date.today()
|
| 12 |
+
dates = [today - timedelta(days=i) for i in range(90)][::-1]
|
| 13 |
+
prices = []
|
| 14 |
+
random.seed(hash(stock_id))
|
| 15 |
+
current_price = random.uniform(50, 800)
|
| 16 |
+
for d in dates:
|
| 17 |
+
open_price = current_price * random.uniform(0.98, 1.02)
|
| 18 |
+
high_price = open_price * random.uniform(1.0, 1.03)
|
| 19 |
+
low_price = open_price * random.uniform(0.97, 1.0)
|
| 20 |
+
close_price = random.uniform(low_price, high_price)
|
| 21 |
+
prices.append({
|
| 22 |
+
"Date": d, "Open": round(open_price, 2), "High": round(high_price, 2),
|
| 23 |
+
"Low": round(low_price, 2), "Close": round(close_price, 2),
|
| 24 |
+
"Volume": random.randint(1_000_000, 50_000_000)
|
| 25 |
+
})
|
| 26 |
+
current_price = close_price
|
| 27 |
+
return pd.DataFrame(prices)
|
| 28 |
+
|
| 29 |
+
def generate_search_volume_data(stock_id: str) -> pd.DataFrame:
|
| 30 |
+
"""生成模擬的每日搜尋聲量"""
|
| 31 |
+
today = date.today()
|
| 32 |
+
dates = [today - timedelta(days=i) for i in range(90)][::-1]
|
| 33 |
+
volumes = []
|
| 34 |
+
random.seed(hash(stock_id) + 1)
|
| 35 |
+
base_volume = random.randint(20, 60)
|
| 36 |
+
for d in dates:
|
| 37 |
+
spike = random.uniform(15, 40) if random.random() > 0.92 else 0
|
| 38 |
+
volume = base_volume + random.uniform(-5, 5) + spike
|
| 39 |
+
volumes.append({"Date": d, "Search Volume": max(10, round(volume))})
|
| 40 |
+
return pd.DataFrame(volumes)
|
| 41 |
+
|
| 42 |
+
def generate_financial_data(stock_id: str) -> pd.DataFrame:
|
| 43 |
+
"""生成模擬的季度財務報表"""
|
| 44 |
+
random.seed(hash(stock_id))
|
| 45 |
+
data = {
|
| 46 |
+
"項目": ["營業收入 (百萬)", "營業毛利 (百萬)", "稅後淨利 (百萬)", "每股盈餘 (EPS)"],
|
| 47 |
+
"2024 Q3": [random.randint(8000, 12000), random.randint(3000, 5000), random.randint(1500, 3000), round(random.uniform(1, 5), 2)],
|
| 48 |
+
"2024 Q2": [random.randint(7500, 11000), random.randint(2800, 4800), random.randint(1400, 2800), round(random.uniform(0.8, 4.5), 2)],
|
| 49 |
+
"2024 Q1": [random.randint(7000, 10000), random.randint(2500, 4500), random.randint(1200, 2500), round(random.uniform(0.7, 4.0), 2)],
|
| 50 |
+
}
|
| 51 |
+
return pd.DataFrame(data)
|
| 52 |
+
|
| 53 |
+
def generate_institutional_trades(stock_id: str) -> pd.DataFrame:
|
| 54 |
+
"""生成模擬的法人買賣資料"""
|
| 55 |
+
today = date.today()
|
| 56 |
+
institutions = ["外資", "投信", "自營商"]
|
| 57 |
+
actions = ["買超", "賣超"]
|
| 58 |
+
records = []
|
| 59 |
+
for i in range(15):
|
| 60 |
+
records.append({
|
| 61 |
+
"日期": today - timedelta(days=i), "法人機構": random.choice(institutions),
|
| 62 |
+
"買賣超": random.choice(actions), "張數": random.randint(100, 5000)
|
| 63 |
+
})
|
| 64 |
+
return pd.DataFrame(records)
|
| 65 |
+
|
| 66 |
+
def generate_recent_news(stock_id: str) -> list:
|
| 67 |
+
"""生成模擬的近期新聞,包含標題和內容"""
|
| 68 |
+
random.seed(hash(stock_id))
|
| 69 |
+
headlines = [
|
| 70 |
+
{
|
| 71 |
+
"headline": f"【產業動態】分析師看好 {stock_id} 第四季度的市場表現",
|
| 72 |
+
"content": f"根據最新的市場分析報告,由於全球供應鏈問題緩解以及終端需求回溫,分析師普遍預期 {stock_id} 在第四季度的營收將季增15%,達到歷史新高。報告中特別指出,該公司在新興市場的佈局已見成效,有望成為下一波成長的主要動能。"
|
| 73 |
+
},
|
| 74 |
+
{
|
| 75 |
+
"headline": f"【公司新聞】{stock_id} 宣布將於下月發布新一代 AI 晶片",
|
| 76 |
+
"content": f"{stock_id} 今日向媒體發出邀請函,確認將於下月15日舉行年度產品發表會。市場預期,會中的最大亮點將是代號為 'Phoenix' 的新一代 AI 運算晶片。據傳該晶片的效能較前代提升了70%,並大幅降低功耗,有望鞏固其在雲端運算市場的領導地位。"
|
| 77 |
+
},
|
| 78 |
+
{
|
| 79 |
+
"headline": f"【法人觀點】某外資機構上調 {stock_id} 評級至「強力買入」",
|
| 80 |
+
"content": f"知名外資機構今日發布研究報告,將 {stock_id} 的股票評級從「中立」一口氣上調至「強力買入」,目標價也提高了30%。報告認為,市場低估了 {stock_id} 在汽車電子領域的潛力,預計相關業務將在未來三年內貢獻超過20%的總營收。"
|
| 81 |
+
}
|
| 82 |
+
]
|
| 83 |
+
random.shuffle(headlines)
|
| 84 |
+
return headlines
|
| 85 |
+
|
| 86 |
+
# --- 2. 主要處理函式 ---
|
| 87 |
+
|
| 88 |
+
def get_stock_info(stock_id: str):
|
| 89 |
+
"""根據股票代號獲取所有資訊並生成圖表和資料"""
|
| 90 |
+
if not stock_id:
|
| 91 |
+
empty_fig = go.Figure()
|
| 92 |
+
empty_df = pd.DataFrame()
|
| 93 |
+
# 返回與輸出元件數量相符的空更新
|
| 94 |
+
return (
|
| 95 |
+
empty_fig, empty_fig, empty_df, empty_df,
|
| 96 |
+
gr.update(visible=False), gr.update(value=""),
|
| 97 |
+
gr.update(visible=False), gr.update(value=""),
|
| 98 |
+
gr.update(visible=False), gr.update(value="")
|
| 99 |
+
)
|
| 100 |
+
|
| 101 |
+
# 生成資料
|
| 102 |
+
price_df = generate_price_data(stock_id)
|
| 103 |
+
search_volume_df = generate_search_volume_data(stock_id)
|
| 104 |
+
financial_df = generate_financial_data(stock_id)
|
| 105 |
+
trades_df = generate_institutional_trades(stock_id)
|
| 106 |
+
news_list = generate_recent_news(stock_id)
|
| 107 |
+
|
| 108 |
+
# 創建 K線圖
|
| 109 |
+
candlestick_fig = go.Figure(data=[go.Candlestick(
|
| 110 |
+
x=price_df['Date'], open=price_df['Open'], high=price_df['High'],
|
| 111 |
+
low=price_df['Low'], close=price_df['Close'], name='K線'
|
| 112 |
+
)])
|
| 113 |
+
candlestick_fig.update_layout(
|
| 114 |
+
xaxis_title='日期', yaxis_title='價格', xaxis_rangeslider_visible=False,
|
| 115 |
+
template="plotly_dark", margin=dict(l=40, r=40, t=40, b=40)
|
| 116 |
+
)
|
| 117 |
+
|
| 118 |
+
# 創建搜尋聲量折線圖
|
| 119 |
+
search_volume_fig = go.Figure(data=[go.Scatter(
|
| 120 |
+
x=search_volume_df['Date'], y=search_volume_df['Search Volume'],
|
| 121 |
+
mode='lines', name='聲量', line=dict(color='cyan')
|
| 122 |
+
)])
|
| 123 |
+
search_volume_fig.update_layout(
|
| 124 |
+
xaxis_title='日期', yaxis_title='相對搜尋熱度', template="plotly_dark",
|
| 125 |
+
margin=dict(l=40, r=40, t=40, b=40)
|
| 126 |
+
)
|
| 127 |
+
|
| 128 |
+
# 返回所有元件的更新值
|
| 129 |
+
return (
|
| 130 |
+
candlestick_fig, search_volume_fig, financial_df, trades_df,
|
| 131 |
+
gr.update(label=news_list[0]['headline'], visible=True),
|
| 132 |
+
gr.update(value=news_list[0]['content']),
|
| 133 |
+
gr.update(label=news_list[1]['headline'], visible=True),
|
| 134 |
+
gr.update(value=news_list[1]['content']),
|
| 135 |
+
gr.update(label=news_list[2]['headline'], visible=True),
|
| 136 |
+
gr.update(value=news_list[2]['content'])
|
| 137 |
+
)
|
| 138 |
+
|
| 139 |
+
# --- 3. Gradio 介面 ---
|
| 140 |
+
|
| 141 |
+
with gr.Blocks(theme=gr.themes.Soft(), title="股票資訊查詢系統") as demo:
|
| 142 |
+
gr.Markdown("# 📈 股票資訊查詢系統")
|
| 143 |
+
gr.Markdown("輸入股票代號 (例如: 2330.TW, AAPL),然後按 Enter 或點擊送出按鈕來查詢相關資訊。")
|
| 144 |
+
|
| 145 |
+
with gr.Row():
|
| 146 |
+
stock_input = gr.Textbox(label="股票代號", placeholder="例如: 2330.TW 或 AAPL", scale=4)
|
| 147 |
+
submit_btn = gr.Button("送出查詢", scale=1)
|
| 148 |
+
|
| 149 |
+
gr.Markdown("---")
|
| 150 |
+
gr.Markdown("### 📊 股價 K線圖")
|
| 151 |
+
price_plot = gr.Plot()
|
| 152 |
+
|
| 153 |
+
gr.Markdown("### 📈 網路搜尋聲量")
|
| 154 |
+
search_volume_plot = gr.Plot()
|
| 155 |
+
|
| 156 |
+
gr.Markdown("### 📄 財務報表")
|
| 157 |
+
financial_output = gr.Dataframe(interactive=False, wrap=True)
|
| 158 |
+
|
| 159 |
+
gr.Markdown("### 🏢 法人買賣")
|
| 160 |
+
trades_output = gr.Dataframe(interactive=False, wrap=True)
|
| 161 |
+
|
| 162 |
+
gr.Markdown("### 📰 近期新聞")
|
| 163 |
+
with gr.Column():
|
| 164 |
+
news_accordion_1 = gr.Accordion("新聞標題 1", visible=False)
|
| 165 |
+
with news_accordion_1:
|
| 166 |
+
news_content_1 = gr.Markdown()
|
| 167 |
+
|
| 168 |
+
news_accordion_2 = gr.Accordion("新聞標題 2", visible=False)
|
| 169 |
+
with news_accordion_2:
|
| 170 |
+
news_content_2 = gr.Markdown()
|
| 171 |
+
|
| 172 |
+
news_accordion_3 = gr.Accordion("新聞標題 3", visible=False)
|
| 173 |
+
with news_accordion_3:
|
| 174 |
+
news_content_3 = gr.Markdown()
|
| 175 |
+
|
| 176 |
+
outputs_list = [
|
| 177 |
+
price_plot, search_volume_plot, financial_output, trades_output,
|
| 178 |
+
news_accordion_1, news_content_1,
|
| 179 |
+
news_accordion_2, news_content_2,
|
| 180 |
+
news_accordion_3, news_content_3
|
| 181 |
+
]
|
| 182 |
+
|
| 183 |
+
submit_btn.click(fn=get_stock_info, inputs=stock_input, outputs=outputs_list)
|
| 184 |
+
stock_input.submit(fn=get_stock_info, inputs=stock_input, outputs=outputs_list)
|
| 185 |
+
|
| 186 |
+
if __name__ == "__main__":
|
| 187 |
+
demo.launch()
|