TW_STOCK_WITH / app.py
Rooobert's picture
Update app.py
99eeda2 verified
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()