import streamlit as st import yfinance as yf import twstock import plotly.graph_objects as go from plotly.subplots import make_subplots import pandas as pd from datetime import datetime, timedelta def plot_stock_data(stock_symbols, period='1y'): """ 繪製股票價格圖表 :param stock_symbols: 股票代號列表 :param period: 時間區間 :return: Plotly figure """ # 創建子圖 fig = make_subplots( rows=len(stock_symbols), cols=1, subplot_titles=[f"股價走勢: {symbol}" for symbol in stock_symbols], vertical_spacing=0.05, specs=[[{"secondary_y": True}] for _ in stock_symbols] ) # 為每個股票繪製圖形 for idx, symbol in enumerate(stock_symbols, 1): try: # 獲取股票數據 stock = yf.Ticker(symbol) df = stock.history(period=period) if df.empty: st.warning(f"無法獲取 {symbol} 的股票數據") continue # 添加蠟燭圖 fig.add_trace( go.Candlestick( x=df.index, open=df['Open'], high=df['High'], low=df['Low'], close=df['Close'], name=f'{symbol} 價格' ), row=idx, col=1 ) # 添加成交量柱狀圖 fig.add_trace( go.Bar( x=df.index, y=df['Volume'], name=f'{symbol} 成交量', opacity=0.3 ), row=idx, col=1, secondary_y=True ) # 添加移動平均線 for ma_days in [5, 20, 60]: ma = df['Close'].rolling(window=ma_days).mean() fig.add_trace( go.Scatter( x=df.index, y=ma, name=f'{symbol} MA{ma_days}', line=dict(width=1) ), row=idx, col=1 ) except Exception as e: st.error(f"處理 {symbol} 時發生錯誤: {str(e)}") # 更新布局 fig.update_layout( height=400 * len(stock_symbols), title_text="台股分析圖", showlegend=True, xaxis_rangeslider_visible=False, template="plotly_white" ) # 更新軸標籤 for i in range(1, len(stock_symbols) + 1): fig.update_xaxes(title_text="日期", row=i, col=1) fig.update_yaxes(title_text="價格 (TWD)", row=i, col=1) fig.update_yaxes(title_text="成交量", row=i, col=1, secondary_y=True) return fig def fetch_recent_stock_data(stock_code): """ 使用 twstock 獲取近 30 天的交易數據 """ try: # 使用 twstock 獲取近 30 天的交易數據 stock = twstock.Stock(stock_code) recent_data = stock.fetch_31() # 抓取最近 31 天的交易數據 if not recent_data: st.warning(f"無法找到 {stock_code} 的交易數據。") return None # 將數據整理為 DataFrame 格式 data_list = [ { "Date": data.date.strftime('%Y-%m-%d'), # 日期 "Open": data.open, # 開盤價 "High": data.high, # 最高價 "Low": data.low, # 最低價 "Close": data.close, # 收盤價 "Transaction": data.transaction, # 成交筆數 "Capacity": data.capacity, # 成交股數 "Turnover": data.turnover # 成交金額 } for data in recent_data ] df = pd.DataFrame(data_list) return df except Exception as e: st.error(f"發生錯誤: {e}") st.error("請確認股票代碼是否正確,或是否為台股上市/上櫃股票。") return None def main(): st.set_page_config(page_title="台股分析工具", layout="wide") st.title("台股分析工具") # 選擇分析模式 mode = st.sidebar.radio("選擇分析模式", ["歷史股價圖", "近期交易資料"]) if mode == "歷史股價圖": # 股票代號輸入 stock_input = st.text_input( "股票代號 (用逗號分隔)", value="2330.TW,2454.TW", placeholder="例如: 2330,2454" ) # 時間區間選擇 period_select = st.selectbox( "時間區間", ["1mo", "3mo", "6mo", "1y", "2y", "5y", "max"], index=3 # 預設為 1y ) # 處理股票代號 stocks = [s.strip() for s in stock_input.split(',')] stocks = [f"{s}.TW" if not s.endswith('.TW') and s.isdigit() else s for s in stocks] # 繪製圖表按鈕 if st.button("繪製圖表"): fig = plot_stock_data(stocks, period_select) st.plotly_chart(fig, use_container_width=True) else: # 近期交易資料模式 stock_code = st.text_input("請輸入股票代碼", value="2330") if st.button("查詢資料"): df = fetch_recent_stock_data(stock_code) if df is not None: # 顯示 DataFrame st.subheader(f"股票代碼: {stock_code} - 最近30天交易數據") st.dataframe(df) # 基本統計 st.subheader("基本統計") col1, col2, col3 = st.columns(3) col1.metric("平均收盤價", f"{df['Close'].mean():.2f}") col2.metric("最高價", f"{df['High'].max():.2f}") col3.metric("最低價", f"{df['Low'].min():.2f}") # 下載 CSV csv = df.to_csv(index=False, encoding="utf-8-sig") st.download_button( label="下載 CSV 檔案", data=csv, file_name=f"{stock_code}_recent_30days.csv", mime="text/csv" ) if __name__ == "__main__": main()