import gradio as gr import yfinance as yf import pandas as pd import plotly.graph_objs as go from plotly.subplots import make_subplots def fetch_etf_data(ticker_codes): """ 抓取台灣ETF歷史數據 參數: ticker_codes (str): ETF代碼,逗號分隔 返回: tuple: 數據框和圖表 """ # 拆分代碼 ticker_list = [code.strip() for code in ticker_codes.split(',')] # 儲存所有ETF的數據 results = {} # 創建子圖 fig = make_subplots( rows=len(ticker_list), cols=1, shared_xaxes=True, vertical_spacing=0.02, subplot_titles=[f"{code}.TW" for code in ticker_list] ) # 顏色列表 colors = ['blue', 'green', 'red', 'purple', 'orange'] # 遍歷每個ETF代碼 output_text = "" for i, ticker_code in enumerate(ticker_list, 1): try: # 添加 ".TW" 後綴(台灣交易所) full_ticker = f"{ticker_code}.TW" # 獲取ETF數據 etf = yf.Ticker(full_ticker) # 獲取1年歷史數據 df = etf.history(period="1y") # 如果數據為空,跳過此ETF if df.empty: output_text += f"警告:未找到 {full_ticker} 的數據\n" continue # 儲存數據 results[ticker_code] = df # 添加K線圖 fig.add_trace( go.Candlestick( x=df.index, open=df['Open'], high=df['High'], low=df['Low'], close=df['Close'], name=full_ticker, increasing_line_color=colors[i % len(colors)], decreasing_line_color='gray' ), row=i, col=1 ) # 添加成交量柱狀圖 fig.add_trace( go.Bar( x=df.index, y=df['Volume'], name=f'{full_ticker} 成交量', opacity=0.5 ), row=i, col=1 ) # 生成文本輸出 output_text += f"\n{full_ticker} 最近數據:\n" output_text += df.tail().to_string() + "\n" except Exception as e: output_text += f"{ticker_code} 發生錯誤: {e}\n" # 更新佈局 fig.update_layout( height=300 * len(ticker_list), # 根據ETF數量調整高度 title='台灣ETF走勢圖', xaxis_rangeslider_visible=False # 關閉範圍滑塊 ) # 合併CSV if results: combined_df = pd.concat([df for df in results.values()], keys=results.keys(), names=['ETF', 'index']) combined_df.to_csv("combined_etf_data.csv", encoding='utf-8-sig') output_text += "\n所有ETF數據已合併保存到 combined_etf_data.csv" return fig, output_text def create_gradio_interface(): """ 創建Gradio界面 """ with gr.Blocks(title="台灣ETF數據分析") as demo: gr.Markdown("## 台灣ETF數據抓取與分析") with gr.Row(): with gr.Column(): # 輸入框 ticker_input = gr.Textbox( label="輸入ETF代碼", placeholder="例如:00878, 00940, 0050", value="00878, 00940, 0050" ) # 提交按鈕 submit_btn = gr.Button("分析ETF") with gr.Column(): # 文本輸出 output_text = gr.Textbox(label="分析結果") # 圖表輸出 output_plot = gr.Plot(label="ETF走勢圖") # 提交事件 submit_btn.click( fn=fetch_etf_data, inputs=ticker_input, outputs=[output_plot, output_text] ) return demo # 啟動Gradio應用 def main(): demo = create_gradio_interface() demo.launch( share=True, # 創建公開連結 server_name='0.0.0.0', # 允許外部訪問 server_port=7860 # 指定端口 ) # 運行主程序 if __name__ == "__main__": main()