jeff7522553 commited on
Commit
32adef4
·
1 Parent(s): 0dee4d1
Files changed (1) hide show
  1. app.py +187 -0
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()