AlanRex commited on
Commit
e59eb7c
·
verified ·
1 Parent(s): b2e231a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +29 -128
app.py CHANGED
@@ -13,72 +13,24 @@ from plotly.subplots import make_subplots
13
  import re
14
  from bs4 import BeautifulSoup
15
  import requests
16
- import time # 引用 time 模組以處理時間戳
17
 
18
  # 引用您組員的預測器程式
19
  from Bert_predict import BertPredictor
20
 
21
- # ========================= CACHE 設定 START =========================
22
- # 分析結果的快取字典
23
- ANALYSIS_CACHE = {}
24
- # 快取有效時間(秒),例如:4 小時 = 4 * 60 * 60 = 14400 秒
25
- CACHE_DURATION_SECONDS = 8 * 60 * 60
26
- # ========================== CACHE 設定 END ==========================
27
-
28
-
29
  # 台股代號對應表 (移除台指期,因為它現在是獨立區塊)
30
  TAIWAN_STOCKS = {
31
- '元大台灣50': '0050.TW',
32
  '台積電': '2330.TW',
33
  '聯發科': '2454.TW',
34
  '鴻海': '2317.TW',
35
- '台達電': '2308.TW',
36
- '廣達': '2382.TW',
37
  '富邦金': '2881.TW',
38
- '中信金': '2891.TW',
39
  '國泰金': '2882.TW',
40
- '聯電': '2303.TW',
41
- '中華電': '2412.TW',
42
- '玉山金': '2884.TW',
43
- '兆豐金': '2886.TW',
44
- '日月光投控': '3711.TW',
45
- '華碩': '2357.TW',
46
  '統一': '1216.TW',
47
- '元大金': '2885.TW',
48
- '智邦': '2345.TW',
49
- '緯創': '3231.TW',
50
- '聯詠': '3034.TW',
51
- '第一金': '2892.TW',
52
- '瑞昱': '2379.TW',
53
- '緯穎': '6669.TWO',
54
- '永豐金': '2890.TW',
55
- '合庫金': '5880.TW',
56
- '華南金': '2880.TW',
57
- '台光電': '2383.TW',
58
- '世芯-KY': '3661.TWO',
59
- '奇鋐': '3017.TW',
60
- '凱基金': '2883.TW',
61
- '大立光': '3008.TW',
62
  '長榮': '2603.TW',
63
- '光寶科': '2301.TW',
64
- '中鋼': '2002.TW',
65
- '中租-KY': '5871.TW',
66
- '國巨': '2327.TW',
67
- '台新金': '2887.TW',
68
- '上海商銀': '5876.TW',
69
- '台泥': '1101.TW',
70
- '台灣大': '3045.TW',
71
- '和碩': '4938.TW',
72
- '遠傳': '4904.TW',
73
- '和泰車': '2207.TW',
74
- '研華': '2395.TW',
75
- '台塑': '1301.TW',
76
- '統一超': '2912.TW',
77
- '藥華藥': '6446.TWO',
78
- '南亞': '1303.TW',
79
- '陽明': '2609.TW',
80
- '萬海': '2615.TW',
81
- '台塑化': '6505.TW',
82
  '慧洋-KY': '2637.TW',
83
  '上銀': '2049.TW',
84
  '台泥': '1101.TW',
@@ -92,57 +44,18 @@ TAIWAN_STOCKS = {
92
 
93
  # 產業分類
94
  INDUSTRY_MAPPING = {
95
- '0050.TW': 'ETF',
96
  '2330.TW': '半導體',
97
  '2454.TW': '半導體',
98
  '2317.TW': '電子組件',
99
- '2308.TW': '電子',
100
- '2382.TW': '電子',
101
  '2881.TW': '金融',
102
- '2891.TW': '金融',
103
  '2882.TW': '金融',
104
- '2303.TW': '半導體',
105
- '2412.TW': '電信',
106
- '2884.TW': '金融',
107
- '2886.TW': '金融',
108
- '3711.TW': '半導體',
109
- '2357.TW': '電子',
110
  '1216.TW': '食品',
111
- '2885.TW': '金融',
112
- '2345.TW': '網通設備',
113
- '3231.TW': '電子',
114
- '3034.TW': '半導體',
115
- '2892.TW': '金融',
116
- '2379.TW': '半導體',
117
- '6669.TWO': '電子',
118
- '2890.TW': '金融',
119
- '5880.TW': '金融',
120
- '2880.TW': '金融',
121
- '2383.TW': '電子',
122
- '3661.TWO': '半導體',
123
- '3017.TW': '電子',
124
- '2883.TW': '金融',
125
- '3008.TW': '光學',
126
  '2603.TW': '航運',
127
- '2301.TW': '電子',
128
- '2002.TW': '鋼鐵',
129
- '5871.TW': '金融',
130
- '2327.TW': '電子被動元件',
131
- '2887.TW': '金融',
132
- '5876.TW': '金融',
133
- '1101.TW': '營建',
134
- '3045.TW': '電信',
135
- '4938.TW': '電子',
136
- '4904.TW': '電信',
137
- '2207.TW': '汽車',
138
- '2395.TW': '電腦周邊',
139
- '1301.TW': '塑膠',
140
- '2912.TW': '百貨',
141
- '6446.TWO': '生技',
142
- '1303.TW': '塑膠',
143
- '2609.TW': '航運',
144
- '2615.TW': '航運',
145
- '6505.TW': '塑膠',
146
  '2637.TW': '散裝航運',
147
  '2049.TW': '工具機',
148
  '1101.TW': '營建',
@@ -274,6 +187,7 @@ def get_pmi_data():
274
  print(f"無法獲取 PMI 資料: {str(e)}")
275
  return pd.DataFrame()
276
 
 
277
  def generate_gemini_analysis(stock_name, stock_symbol, period, data):
278
  """
279
  使用 Gemini API 生成基本面和市場展望分析。
@@ -286,6 +200,7 @@ def generate_gemini_analysis(stock_name, stock_symbol, period, data):
286
  genai.configure(api_key=api_key)
287
  model = genai.GenerativeModel('gemini-1.5-flash')
288
 
 
289
  price_change = ((data['Close'].iloc[-1] - data['Close'].iloc[0]) / data['Close'].iloc[0]) * 100
290
  rsi_current = data['RSI'].iloc[-1]
291
  macd_current = data['MACD'].iloc[-1]
@@ -327,13 +242,13 @@ def generate_gemini_analysis(stock_name, stock_symbol, period, data):
327
  market_outlook = parts[1].strip()
328
  return dcc.Markdown(fundamental_analysis), dcc.Markdown(market_outlook)
329
  else:
330
- # Fallback for unexpected response format
331
- return dcc.Markdown("無法解析 Gemini 回應,請稍後再試。"), dcc.Markdown(response.text)
332
 
333
  except Exception as e:
334
  error_message = f"呼叫 Gemini API 時發生錯誤: {str(e)}"
335
  print(error_message)
336
- return dcc.Markdown(error_message), dcc.Markdown("請檢查後台日誌或 API 金鑰設定")
 
337
 
338
  # 建立 Dash 應用程式
339
  app = dash.Dash(__name__, suppress_callback_exceptions=True)
@@ -397,7 +312,7 @@ app.layout = html.Div([
397
  html.Label("時間範圍:"),
398
  dcc.Dropdown(id='period-dropdown',
399
  options=[{'label': '1個月', 'value': '1mo'},{'label': '3個月', 'value': '3mo'},{'label': '6個月', 'value': '6mo'},{'label': '1年', 'value': '1y'},{'label': '2年', 'value': '2y'}],
400
- value='1mo', style={'margin-bottom': '10px'})
401
  ], style={'width': '30%', 'display': 'inline-block', 'margin-left': '5%', 'vertical-align': 'top'}),
402
  html.Div([
403
  html.Label("圖表類型:"),
@@ -676,7 +591,7 @@ def update_business_climate_chart(selected_stock):
676
  fig.update_layout(title="台灣景氣燈號走勢", xaxis_title='日期', yaxis_title='燈號分數', height=300, yaxis=dict(range=[0, 40]))
677
  return fig
678
 
679
- # ========================= MODIFIED SECTION START (CACHE INTEGRATED) =========================
680
  @app.callback(
681
  [dash.dependencies.Output('technical-analysis-text', 'children'),
682
  dash.dependencies.Output('fundamental-analysis-text', 'children'),
@@ -685,29 +600,14 @@ def update_business_climate_chart(selected_stock):
685
  dash.dependencies.Input('period-dropdown', 'value')]
686
  )
687
  def update_analysis_text(selected_stock, period):
688
- # 建立快取的唯一鍵值
689
- cache_key = f"{selected_stock}-{period}"
690
- current_time = time.time()
691
-
692
- # 1. 檢查快取
693
- if cache_key in ANALYSIS_CACHE:
694
- cached_data = ANALYSIS_CACHE[cache_key]
695
- if current_time - cached_data['timestamp'] < CACHE_DURATION_SECONDS:
696
- print(f"從快取載入分析: {cache_key}")
697
- # 直接回傳快取的內容
698
- return cached_data['technical'], cached_data['fundamental'], cached_data['outlook']
699
-
700
- print(f"重新生成分析: {cache_key}")
701
- # --- 如果快取沒有,才繼續執行以下程式 ---
702
-
703
  data = get_stock_data(selected_stock, period)
704
  stock_name = [name for name, symbol in TAIWAN_STOCKS.items() if symbol == selected_stock][0]
705
- if data.empty or len(data) < 20:
706
  return "資料不足,無法分析", "資料不足,無法分析", "資料不足,無法分析"
707
 
708
  data = calculate_technical_indicators(data)
709
 
710
- # 2. 技術面分析
711
  price_change = ((data['Close'].iloc[-1] - data['Close'].iloc[0]) / data['Close'].iloc[0]) * 100
712
  rsi_current = data['RSI'].iloc[-1] if not pd.isna(data['RSI'].iloc[-1]) else 50
713
  macd_current = data['MACD'].iloc[-1] if not pd.isna(data['MACD'].iloc[-1]) else 0
@@ -719,16 +619,17 @@ def update_analysis_text(selected_stock, period):
719
  html.P([html.Strong("MACD 指標:"), f"MACD 快線 ({macd_current:.3f}) 目前", html.Span("高於" if macd_current > macd_signal_current else "低於", style={'color': 'red' if macd_current > macd_signal_current else 'green', 'font-weight': 'bold'}), f" Signal 慢線 ({macd_signal_current:.3f}),", f"顯示市場動能偏向{'多頭' if macd_current > macd_signal_current else '空頭'}。"]),
720
  ])
721
 
722
- # 3. 基本面與展望分析 (呼叫 Gemini)
723
- fundamental_text, market_outlook_text = generate_gemini_analysis(stock_name, selected_stock, period, data)
 
 
 
724
 
725
- # 4. 將新產生的結果存入快取
726
- ANALYSIS_CACHE[cache_key] = {
727
- 'technical': technical_text,
728
- 'fundamental': fundamental_text,
729
- 'outlook': market_outlook_text,
730
- 'timestamp': current_time
731
- }
732
 
733
  return technical_text, fundamental_text, market_outlook_text
734
  # ========================== MODIFIED SECTION END ==========================
 
13
  import re
14
  from bs4 import BeautifulSoup
15
  import requests
 
16
 
17
  # 引用您組員的預測器程式
18
  from Bert_predict import BertPredictor
19
 
 
 
 
 
 
 
 
 
20
  # 台股代號對應表 (移除台指期,因為它現在是獨立區塊)
21
  TAIWAN_STOCKS = {
22
+ '元大台灣50': '0050.TW',
23
  '台積電': '2330.TW',
24
  '聯發科': '2454.TW',
25
  '鴻海': '2317.TW',
26
+ '台塑': '1301.TW',
27
+ '中華電': '2412.TW',
28
  '富邦金': '2881.TW',
 
29
  '國泰金': '2882.TW',
30
+ '台達電': '2308.TW',
 
 
 
 
 
31
  '統一': '1216.TW',
32
+ '日月光': '3711.TW',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  '長榮': '2603.TW',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  '慧洋-KY': '2637.TW',
35
  '上銀': '2049.TW',
36
  '台泥': '1101.TW',
 
44
 
45
  # 產業分類
46
  INDUSTRY_MAPPING = {
47
+ '0050.TW': 'ETF',
48
  '2330.TW': '半導體',
49
  '2454.TW': '半導體',
50
  '2317.TW': '電子組件',
51
+ '1301.TW': '塑膠',
52
+ '2412.TW': '電信',
53
  '2881.TW': '金融',
 
54
  '2882.TW': '金融',
55
+ '2308.TW': '電子',
 
 
 
 
 
56
  '1216.TW': '食品',
57
+ '3711.TW': '半導體',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  '2603.TW': '航運',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  '2637.TW': '散裝航運',
60
  '2049.TW': '工具機',
61
  '1101.TW': '營建',
 
187
  print(f"無法獲取 PMI 資料: {str(e)}")
188
  return pd.DataFrame()
189
 
190
+ # ========================= GEMINI 整合 START =========================
191
  def generate_gemini_analysis(stock_name, stock_symbol, period, data):
192
  """
193
  使用 Gemini API 生成基本面和市場展望分析。
 
200
  genai.configure(api_key=api_key)
201
  model = genai.GenerativeModel('gemini-1.5-flash')
202
 
203
+ # 準備傳送給模型的數據
204
  price_change = ((data['Close'].iloc[-1] - data['Close'].iloc[0]) / data['Close'].iloc[0]) * 100
205
  rsi_current = data['RSI'].iloc[-1]
206
  macd_current = data['MACD'].iloc[-1]
 
242
  market_outlook = parts[1].strip()
243
  return dcc.Markdown(fundamental_analysis), dcc.Markdown(market_outlook)
244
  else:
245
+ return "無法解析 Gemini 回應", response.text
 
246
 
247
  except Exception as e:
248
  error_message = f"呼叫 Gemini API 時發生錯誤: {str(e)}"
249
  print(error_message)
250
+ return error_message, "請檢查後台日誌或 API 金鑰設定"
251
+ # ========================== GEMINI 整合 END ==========================
252
 
253
  # 建立 Dash 應用程式
254
  app = dash.Dash(__name__, suppress_callback_exceptions=True)
 
312
  html.Label("時間範圍:"),
313
  dcc.Dropdown(id='period-dropdown',
314
  options=[{'label': '1個月', 'value': '1mo'},{'label': '3個月', 'value': '3mo'},{'label': '6個月', 'value': '6mo'},{'label': '1年', 'value': '1y'},{'label': '2年', 'value': '2y'}],
315
+ value='1mo', style={'margin-bottom': '10px'}) # 預設改為 1mo
316
  ], style={'width': '30%', 'display': 'inline-block', 'margin-left': '5%', 'vertical-align': 'top'}),
317
  html.Div([
318
  html.Label("圖表類型:"),
 
591
  fig.update_layout(title="台灣景氣燈號走勢", xaxis_title='日期', yaxis_title='燈號分數', height=300, yaxis=dict(range=[0, 40]))
592
  return fig
593
 
594
+ # ========================= MODIFIED SECTION START =========================
595
  @app.callback(
596
  [dash.dependencies.Output('technical-analysis-text', 'children'),
597
  dash.dependencies.Output('fundamental-analysis-text', 'children'),
 
600
  dash.dependencies.Input('period-dropdown', 'value')]
601
  )
602
  def update_analysis_text(selected_stock, period):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
603
  data = get_stock_data(selected_stock, period)
604
  stock_name = [name for name, symbol in TAIWAN_STOCKS.items() if symbol == selected_stock][0]
605
+ if data.empty or len(data) < 20: # 確保有足夠資料計算指標
606
  return "資料不足,無法分析", "資料不足,無法分析", "資料不足,無法分析"
607
 
608
  data = calculate_technical_indicators(data)
609
 
610
+ # 1. 技術面分析 (保留客觀數據呈現)
611
  price_change = ((data['Close'].iloc[-1] - data['Close'].iloc[0]) / data['Close'].iloc[0]) * 100
612
  rsi_current = data['RSI'].iloc[-1] if not pd.isna(data['RSI'].iloc[-1]) else 50
613
  macd_current = data['MACD'].iloc[-1] if not pd.isna(data['MACD'].iloc[-1]) else 0
 
619
  html.P([html.Strong("MACD 指標:"), f"MACD 快線 ({macd_current:.3f}) 目前", html.Span("高於" if macd_current > macd_signal_current else "低於", style={'color': 'red' if macd_current > macd_signal_current else 'green', 'font-weight': 'bold'}), f" Signal 慢線 ({macd_signal_current:.3f}),", f"顯示市場動能偏向{'多頭' if macd_current > macd_signal_current else '空頭'}。"]),
620
  ])
621
 
622
+ # 2. 基本面與展望分析 (呼叫 Gemini)
623
+ # 顯示“正在生成…”提示,改善使用者體驗
624
+ loading_text = html.Div([
625
+ dcc.Loading(id="loading-analysis", type="dots", children=[html.Div(id="loading-output")])
626
+ ])
627
 
628
+ try:
629
+ fundamental_text, market_outlook_text = generate_gemini_analysis(stock_name, selected_stock, period, data)
630
+ except Exception as e:
631
+ fundamental_text = f"生成分析時發生錯誤: {e}"
632
+ market_outlook_text = "請檢查 API 金鑰或網路連線。"
 
 
633
 
634
  return technical_text, fundamental_text, market_outlook_text
635
  # ========================== MODIFIED SECTION END ==========================