petertulip86 commited on
Commit
c12a50b
·
verified ·
1 Parent(s): f7c2a23

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +89 -178
app.py CHANGED
@@ -2,44 +2,61 @@ import streamlit as st
2
  import yfinance as yf
3
  import plotly.graph_objects as go
4
  from plotly.subplots import make_subplots
 
5
  import pandas as pd
6
- from datetime import datetime, timedelta
7
 
 
8
  st.set_page_config(
9
- page_title="台灣股票比較分析工具",
10
  page_icon="📈",
11
  layout="wide"
12
  )
13
 
14
- # 標題和說明
15
- st.title("台灣股票比較分析工具")
16
- st.markdown("這個應用程式可以比較多個台灣股票的價格趨勢和表現。輸入股票代號並選擇時間範圍來開始分析。")
17
-
18
- def get_stock_data(stock_ids, period="1y"):
19
- """
20
- 獲取股票數據
21
-
22
- 參數:
23
- stock_ids (list): 股票代碼列表
24
- period (str): 時間週期
25
-
26
- 返回:
27
- dict: 股票數據字典
28
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  stock_data = {}
30
-
31
  with st.spinner("正在獲取股票數據..."):
32
  for stock_id in stock_ids:
33
  try:
34
- # 確保台灣股票代碼格式正確
35
- if stock_id.isdigit() or (len(stock_id) <= 4 and stock_id.split('.')[0].isdigit()):
36
- if '.' not in stock_id:
37
- stock_id = f"{stock_id}.TW"
38
-
39
- # 獲取股票數據
40
  stock = yf.Ticker(stock_id)
41
  hist = stock.history(period=period)
42
-
43
  if not hist.empty:
44
  stock_data[stock_id] = hist
45
  st.success(f"成功獲取 {stock_id} 的數據")
@@ -47,187 +64,81 @@ def get_stock_data(stock_ids, period="1y"):
47
  st.error(f"未找到 {stock_id} 的數據")
48
  except Exception as e:
49
  st.error(f"獲取 {stock_id} 數據時出錯: {str(e)}")
50
-
51
  return stock_data
52
 
53
  def normalize_data(df):
54
- """
55
- 將數據正規化,以便比較不同價格範圍的股票
56
-
57
- 參數:
58
- df (DataFrame): 包含收盤價的 DataFrame
59
-
60
- 返回:
61
- DataFrame: 正規化後的數據
62
- """
63
  return df / df.iloc[0] * 100
64
 
65
  def plot_stock_comparison(stock_data):
66
- """
67
- 使用 Plotly 繪製股票比較圖表
68
-
69
- 參數:
70
- stock_data (dict): 股票數據字典
71
- """
72
  if not stock_data:
73
  st.warning("沒有可用的股票數據來繪製圖表")
74
  return
75
-
76
- # 創建圖表
77
- fig = make_subplots(rows=2, cols=1,
78
- shared_xaxes=True,
79
- vertical_spacing=0.1,
80
- subplot_titles=("股價走勢比較", "正規化股價比較 (基準=100)"),
81
- row_heights=[0.6, 0.4])
82
-
83
  colors = ['blue', 'red', 'green', 'purple', 'orange']
84
  color_idx = 0
85
-
86
- # 用於正規化的數據
87
- normalized_data = {}
88
-
89
- # 添加每個股票的數據到圖表
90
  for stock_id, data in stock_data.items():
91
- # 確保數據不為空
92
  if data.empty:
93
  continue
94
-
95
- # 股票名稱顯示
96
- display_name = stock_id
97
-
98
- # 獲取顏色
99
  color = colors[color_idx % len(colors)]
100
  color_idx += 1
101
-
102
- # 添加原始價格折線圖
103
- fig.add_trace(
104
- go.Scatter(
105
- x=data.index,
106
- y=data['Close'],
107
- mode='lines',
108
- name=f"{display_name}",
109
- line=dict(color=color)
110
- ),
111
- row=1, col=1
112
- )
113
-
114
- # 正規化數據
115
- normalized = normalize_data(data['Close'])
116
- normalized_data[stock_id] = normalized
117
-
118
- # 添加正規化折線圖
119
- fig.add_trace(
120
- go.Scatter(
121
- x=data.index,
122
- y=normalized,
123
- mode='lines',
124
- name=f"{display_name} (正規化)",
125
- line=dict(color=color, dash='dot')
126
- ),
127
- row=2, col=1
128
- )
129
-
130
- # 更新布局
131
- fig.update_layout(
132
- title="股票價格比較",
133
- height=800,
134
- legend=dict(
135
- orientation="h",
136
- yanchor="bottom",
137
- y=1.02,
138
- xanchor="right",
139
- x=1
140
- ),
141
- template="plotly_white"
142
- )
143
-
144
- # 更新Y軸標題
145
  fig.update_yaxes(title_text="價格 (TWD)", row=1, col=1)
146
  fig.update_yaxes(title_text="正規化價格 (基準=100)", row=2, col=1)
147
-
148
- # 顯示圖表
149
  st.plotly_chart(fig, use_container_width=True)
150
 
151
- # 側邊欄:股票選擇和參數設定
152
- with st.sidebar:
153
- st.header("設定")
154
-
155
- # 股票選擇
156
- st.subheader("選擇股票")
157
-
158
- default_stocks = ["2330", "2454"]
159
- stock_input = st.text_input(
160
- "輸入股票代碼 (以逗號分隔)",
161
- value="2330,2454",
162
- help="例如: 2330,2454,2317"
163
- )
164
-
165
- # 解析股票代碼
166
- stock_ids = [s.strip() for s in stock_input.split(',') if s.strip()]
167
-
168
- # 時間範圍選擇
169
- st.subheader("時間範圍")
170
- period = st.selectbox(
171
- "選擇時間範圍",
172
- options=["1m", "3m", "6m", "1y", "2y", "5y"],
173
- index=3, # 預設 1年
174
- format_func=lambda x: {
175
- "1m": "1個月",
176
- "3m": "3個月",
177
- "6m": "6個月",
178
- "1y": "1年",
179
- "2y": "2年",
180
- "5y": "5年"
181
- }.get(x, x)
182
- )
183
-
184
- analyze_button = st.button("分析", type="primary", use_container_width=True)
185
-
186
- # 主要內容區域
187
  if analyze_button or st.session_state.get('has_analyzed', False):
188
  st.session_state['has_analyzed'] = True
189
-
190
- # 檢查是否有輸入股票代碼
191
  if not stock_ids:
192
  st.error("請至少輸入一個股票代碼")
193
  elif len(stock_ids) > 5:
194
  st.warning("最多只能比較5個股票,已取前5個")
195
  stock_ids = stock_ids[:5]
196
- else:
197
- # 顯示所選的股票和時間範圍
198
- st.markdown(f"### 分析 {', '.join(stock_ids)} 的股價表現")
199
- st.markdown(f"時間範圍: {period}")
200
-
201
- # 獲取股票數據
202
- stock_data = get_stock_data(stock_ids, period)
203
-
204
- if stock_data:
205
- # 繪製比較圖表
206
  plot_stock_comparison(stock_data)
207
-
208
- # 顯示數據表格
209
- st.subheader("最近交易數據")
210
-
211
  for stock_id, data in stock_data.items():
212
- if not data.empty:
213
- st.markdown(f"**{stock_id} 最近價格**")
214
- # 顯示最近5個交易日的數據
215
- st.dataframe(data.tail(5))
216
- else:
217
- st.error("無法獲取股票數據,請檢查股票代碼是否正確")
 
 
 
 
 
 
218
  else:
219
- # 首次載入時的說明
220
- st.info("👈 請在左側設定要比較的股票和時間範圍,然後點擊「分析」按鈕開始分析")
221
-
222
- # 添加一些使用指南
223
  st.markdown("""
224
- ### 使用指南:
225
- 1. 在側邊欄輸入要比較的股票代碼(逗號分隔)
226
- 2. 選擇分析的時間範圍
227
- 3. 點擊「分析」按鈕查看結果
228
-
229
- ### 支援的功能:
230
- - 同時比較多5股票的價格走勢
231
- - 查看正規化後的價格比較(以便比較不同價格區間的票)
232
- - 選擇不同的時間範圍進行分析
233
- """)
 
 
2
  import yfinance as yf
3
  import plotly.graph_objects as go
4
  from plotly.subplots import make_subplots
5
+ from datetime import datetime
6
  import pandas as pd
7
+ from streamlit_extras.metric_cards import style_metric_cards
8
 
9
+ # 設定頁面
10
  st.set_page_config(
11
+ page_title="台灣股票比較分析儀表板",
12
  page_icon="📈",
13
  layout="wide"
14
  )
15
 
16
+ # 樣式設定
17
+ st.markdown("""
18
+ <style>
19
+ .block-container {
20
+ padding-top: 2rem;
21
+ padding-bottom: 2rem;
22
+ }
23
+ .main-title {
24
+ font-size: 2.5rem;
25
+ font-weight: bold;
26
+ color: #2c3e50;
27
+ }
28
+ .sub-header {
29
+ font-size: 1.3rem;
30
+ color: #7f8c8d;
31
+ margin-bottom: 1.5rem;
32
+ }
33
+ </style>
34
+ """, unsafe_allow_html=True)
35
+
36
+ st.markdown('<div class="main-title">📊 台灣股票比較分析儀表板</div>', unsafe_allow_html=True)
37
+ st.markdown('<div class="sub-header">視覺化多檔股票表現,洞察市場趨勢</div>', unsafe_allow_html=True)
38
+
39
+ # 側邊欄設定
40
+ with st.sidebar:
41
+ st.header("設定選項")
42
+ stock_input = st.text_input("輸入股票代碼 (以逗號分隔)", value="2330,2454", help="例如: 2330,2454,2317")
43
+ stock_ids = [s.strip() for s in stock_input.split(',') if s.strip()]
44
+
45
+ period = st.selectbox("選擇時間範圍", options=["1m", "3m", "6m", "1y", "2y", "5y"], index=3,
46
+ format_func=lambda x: {"1m": "1個月", "3m": "3個月", "6m": "6個月", "1y": "1年", "2y": "2年", "5y": "5年"}.get(x, x))
47
+
48
+ analyze_button = st.button("分析", type="primary", use_container_width=True)
49
+ st.markdown("🕒 資料更新時間: " + datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
50
+
51
+ def get_stock_data(stock_ids, period):
52
  stock_data = {}
 
53
  with st.spinner("正在獲取股票數據..."):
54
  for stock_id in stock_ids:
55
  try:
56
+ if stock_id.isdigit() and '.' not in stock_id:
57
+ stock_id = f"{stock_id}.TW"
 
 
 
 
58
  stock = yf.Ticker(stock_id)
59
  hist = stock.history(period=period)
 
60
  if not hist.empty:
61
  stock_data[stock_id] = hist
62
  st.success(f"成功獲取 {stock_id} 的數據")
 
64
  st.error(f"未找到 {stock_id} 的數據")
65
  except Exception as e:
66
  st.error(f"獲取 {stock_id} 數據時出錯: {str(e)}")
 
67
  return stock_data
68
 
69
  def normalize_data(df):
 
 
 
 
 
 
 
 
 
70
  return df / df.iloc[0] * 100
71
 
72
  def plot_stock_comparison(stock_data):
 
 
 
 
 
 
73
  if not stock_data:
74
  st.warning("沒有可用的股票數據來繪製圖表")
75
  return
76
+
77
+ fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.1,
78
+ subplot_titles=("股價走勢比較", "正規化股價比較 (基準=100)"), row_heights=[0.6, 0.4])
79
+
 
 
 
 
80
  colors = ['blue', 'red', 'green', 'purple', 'orange']
81
  color_idx = 0
82
+
 
 
 
 
83
  for stock_id, data in stock_data.items():
 
84
  if data.empty:
85
  continue
 
 
 
 
 
86
  color = colors[color_idx % len(colors)]
87
  color_idx += 1
88
+
89
+ fig.add_trace(go.Scatter(x=data.index, y=data['Close'], mode='lines', name=stock_id, line=dict(color=color)), row=1, col=1)
90
+ fig.add_trace(go.Bar(x=data.index, y=data['Volume'], name=f"{stock_id} 成交量", marker_color=color, opacity=0.3), row=1, col=1)
91
+ fig.add_trace(go.Scatter(x=data.index, y=normalize_data(data['Close']), mode='lines', name=f"{stock_id} (正規化)", line=dict(color=color, dash='dot')), row=2, col=1)
92
+
93
+ fig.update_layout(title="股票價格比較圖表", height=800, legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1), template="plotly_white")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  fig.update_yaxes(title_text="價格 (TWD)", row=1, col=1)
95
  fig.update_yaxes(title_text="正規化價格 (基準=100)", row=2, col=1)
 
 
96
  st.plotly_chart(fig, use_container_width=True)
97
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  if analyze_button or st.session_state.get('has_analyzed', False):
99
  st.session_state['has_analyzed'] = True
100
+
 
101
  if not stock_ids:
102
  st.error("請至少輸入一個股票代碼")
103
  elif len(stock_ids) > 5:
104
  st.warning("最多只能比較5個股票,已取前5個")
105
  stock_ids = stock_ids[:5]
106
+
107
+ st.markdown(f"### 分析 {', '.join(stock_ids)} 的股價表現")
108
+ st.markdown(f"時間範圍: {period}")
109
+ stock_data = get_stock_data(stock_ids, period)
110
+
111
+ if stock_data:
112
+ tab1, tab2 = st.tabs(["📈 價格趨勢圖", "📊 數據分析表"])
113
+
114
+ with tab1:
 
115
  plot_stock_comparison(stock_data)
116
+
117
+ with tab2:
 
 
118
  for stock_id, data in stock_data.items():
119
+ st.markdown(f"#### {stock_id} 最近 5 日交易資料")
120
+ st.dataframe(data.tail(5))
121
+ latest_close = data['Close'].iloc[-1]
122
+ first_close = data['Close'].iloc[0]
123
+ pct_change = ((latest_close - first_close) / first_close) * 100
124
+ col1, col2, col3 = st.columns(3)
125
+ col1.metric("最新價格", f"{latest_close:.2f} TWD")
126
+ col2.metric("起始價格", f"{first_close:.2f} TWD")
127
+ col3.metric("報酬率", f"{pct_change:.2f}%", delta=f"{pct_change:.2f}%")
128
+ style_metric_cards()
129
+ else:
130
+ st.error("無法獲取股票數據,請檢查股票代碼是否正確")
131
  else:
132
+ st.info("👈 請在左側輸股票代碼與間範圍,然後點擊「分析」開始")
 
 
 
133
  st.markdown("""
134
+ ### 使用說明
135
+ 1. 在側邊欄輸入要比較的股票代碼(逗號分隔)
136
+ 2. 選擇分析的時間範圍
137
+ 3. 點擊「分析」查看結果
138
+
139
+ ### 功能亮點
140
+ - 同時比較 5 支台灣股票走勢
141
+ - 股價與成交量視覺化
142
+ - 正規化股價比較(基準=100)
143
+ - 每支股票關鍵統計指標(報酬率、起始與最新價格)
144
+ """)