AlanRex commited on
Commit
b1a597f
·
verified ·
1 Parent(s): 63332a0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +49 -47
app.py CHANGED
@@ -1,4 +1,4 @@
1
- # HUGING_FACE_V4.2(輕量AI版).py - 已整合 XGBoost 模型
2
 
3
  # 系統套件
4
  import os
@@ -21,12 +21,12 @@ import time # 引用 time 模組以處理時間戳
21
  # 引用您組員的預測器程式
22
  from Bert_predict import BertPredictor
23
 
24
- # 【修改 1】: 匯入 XGBoostModel 類別
25
  from model_predictor import XGBoostModel
26
  # ========================== 引用外部模組 END ==========================
27
 
28
  # ========================= 全域設定 START =========================
29
- # 【修改 2】: 將開關設為 True 來啟用您的 XGBoost 模型
30
  USE_ADVANCED_MODEL = True
31
 
32
  # ========================= CACHE 設定 START =========================
@@ -36,7 +36,7 @@ ANALYSIS_CACHE = {}
36
  CACHE_DURATION_SECONDS = 8 * 60 * 60
37
  # ========================== CACHE 設定 END ==========================
38
 
39
- # 【修改 3】: 在應用程式啟動時,預先載入 XGBoost 模型
40
  try:
41
  print("正在初始化 XGBoost 預測模型...")
42
  xgb_model = XGBoostModel(default_model='xgboost_model')
@@ -118,7 +118,7 @@ def simple_statistical_predict(data, predict_days=5):
118
  change_pct = ((predicted_price - prices[-1]) / prices[-1]) * 100
119
  return {'predicted_price': predicted_price, 'change_pct': change_pct, 'confidence': max(0.6, 1 - volatility * 2)}
120
 
121
- # 【修改 4】: 建立一個新的函式來處理 XGBoost 模型的輸入和輸出
122
  def advanced_xgboost_predict(data, predict_days):
123
  """
124
  【進階模型橋接函式】
@@ -130,41 +130,48 @@ def advanced_xgboost_predict(data, predict_days):
130
  return None
131
 
132
  # 1. 準備輸入資料
133
- # 重要假設:模型是使用與 `get_stock_data` 回傳的 DataFrame 相同的欄位進行訓練的。
134
- # 我們使用最新的資料點來進行未來預測。
 
 
135
  input_df = data.tail(1)
136
 
 
 
 
 
 
 
 
 
137
  try:
138
  # 2. 呼叫模型預測
139
- predictions = xgb_model.predict('xgboost_model', input_df)
140
 
141
  # 3. 根據 predict_days 解析輸出
142
- # 建立預測天數到模型輸出鍵的映射
143
  day_to_key_map = {
144
- 1: 'Close_t0_pred', # 假設 t0 代表 1 天後
145
  5: 'Close_t5_pred',
146
  10: 'Close_t10_pred',
147
  20: 'Close_t20_pred',
148
- 60: None # 您的模型沒有提供60天的預測,這裡設為None
149
  }
150
 
151
- # 找到對應的預測鍵
152
  prediction_key = day_to_key_map.get(predict_days)
153
 
154
  if prediction_key is None or prediction_key not in predictions:
155
  print(f"警告: XGBoost 模型沒有提供 {predict_days} 天的預測結果。")
156
- return None # 如果找不到對應的預測天期,則返回 None
157
 
158
  predicted_price = predictions[prediction_key]
159
  current_price = data['Close'].iloc[-1]
160
  change_pct = ((predicted_price - current_price) / current_price) * 100
161
 
162
  # 4. 包裝成主程式所需的格式
163
- # XGBoost 模型通常不直接提供信心度,這裡我們先給一個固定值
164
  return {
165
  'predicted_price': predicted_price,
166
  'change_pct': change_pct,
167
- 'confidence': 0.95 # 給定一個較高的固定信心度
168
  }
169
  except Exception as e:
170
  print(f"執行 XGBoost 預測時發生錯誤: {e}")
@@ -172,20 +179,17 @@ def advanced_xgboost_predict(data, predict_days):
172
 
173
  def get_prediction(data, predict_days=5):
174
  """
175
- 【【模型預測控制器】】
176
  根據 USE_ADVANCED_MODEL 的設定,呼叫對應的預測模型。
177
  """
178
  if USE_ADVANCED_MODEL:
179
  print(f"模式: 進階XGBoost模型 | 預測天期: {predict_days}天")
180
- # 【修改 5】: 呼叫新的 XGBoost 橋接函式
181
  prediction = advanced_xgboost_predict(data, predict_days)
182
- # 如果進階模型預測失敗,則自動降級使用簡易模型
183
  if prediction is not None:
184
  return prediction
185
  else:
186
  print("進階模型預測失敗或無對應天期,自動降級為簡易統計模型。")
187
 
188
- # 預設或降級時執行簡易模���
189
  print(f"模式: 簡易統計模型 | 預測天期: {predict_days}天")
190
  return simple_statistical_predict(data, predict_days)
191
 
@@ -227,14 +231,32 @@ def calculate_technical_indicators(df):
227
  df['ADX'] = df['DX'].ewm(com=13, adjust=False).mean()
228
  return df
229
 
 
230
  def calculate_volume_profile(df, num_bins=50):
231
  if df.empty or 'High' not in df.columns or 'Low' not in df.columns or 'Volume' not in df.columns: return None, None, None
 
 
232
  all_prices = np.concatenate([df['High'].values, df['Low'].values])
 
 
 
 
 
 
233
  min_price, max_price = all_prices.min(), all_prices.max()
 
 
 
 
 
234
  price_for_volume = (df['High'] + df['Low'] + df['Close']) / 3
235
  df_vol_profile = df.copy()
236
  df_vol_profile['Price_Indicator'] = price_for_volume
237
- hist, bin_edges = np.histogram(df_vol_profile['Price_Indicator'], bins=num_bins, range=(min_price, max_price), weights=df_vol_profile['Volume'])
 
 
 
 
238
  price_centers = (bin_edges[:-1] + bin_edges[1:]) / 2
239
  return bin_edges, hist, price_centers
240
 
@@ -268,9 +290,6 @@ def get_pmi_data():
268
  return pd.DataFrame()
269
 
270
  def generate_gemini_analysis(stock_name, stock_symbol, period, data):
271
- """
272
- 使用 Gemini API 生成基本面和市場展望分析。
273
- """
274
  api_key = os.getenv("GEMINI_API_KEY")
275
  if not api_key:
276
  return "無法讀取 GEMINI API 金鑰", "請在系統環境變數中設定您的金鑰"
@@ -278,7 +297,6 @@ def generate_gemini_analysis(stock_name, stock_symbol, period, data):
278
  try:
279
  genai.configure(api_key=api_key)
280
  model = genai.GenerativeModel('gemini-1.5-flash')
281
-
282
  price_change = ((data['Close'].iloc[-1] - data['Close'].iloc[0]) / data['Close'].iloc[0]) * 100
283
  rsi_current = data['RSI'].iloc[-1]
284
  macd_current = data['MACD'].iloc[-1]
@@ -288,7 +306,6 @@ def generate_gemini_analysis(stock_name, stock_symbol, period, data):
288
  prompt = f"""
289
  請扮演一位專業、資深的台灣股市金融分析師。
290
  我將提供一檔台股的即時技術指標數據,請你基於這些數據,結合你對這家公司、其所在產業以及當前市場趨勢的理解,為我生成一段專業的「基本面分析」和一段「市場展望與投資建議」。
291
-
292
  **股票資訊:**
293
  - **公司名稱:** {stock_name} ({stock_symbol})
294
  - **分析期間:** 最近 {period}
@@ -296,23 +313,19 @@ def generate_gemini_analysis(stock_name, stock_symbol, period, data):
296
  - **期間價格變動:** {price_change:+.2f}%
297
  - **目前 RSI 指標:** {rsi_current:.2f}
298
  - **目前 MACD 指標:** MACD線為 {macd_current:.3f}, 信號線為 {macd_signal_current:.3f}
299
-
300
  **你的任務:**
301
  1. **基本面分析 (約 150 字):**
302
  - 評論這家公司的產業地位、近期營運亮點或挑戰。
303
  - 提及任何可能影響其基本面的關鍵因素 (例如:財報、法說會、政策、供應鏈變化等)。
304
  - 請用專業、客觀的語氣撰寫。
305
-
306
  2. **市場展望與投資建議 (約 150 字):**
307
  - 基於上述所有資訊,提供對該股票的短期和中期市場展望。
308
  - 提出具體的投資建議,例如:適合何種類型的投資人、潛在的風險點。
309
  - 請直接提供分析內容,不要包含任何問候語。
310
-
311
  **輸出格式:**
312
  請嚴格按照以下格式回傳,使用"$$"作為兩個段落之間的分隔符:
313
  [基本面分析內容]$$[市場展望與投資建議內容]
314
  """
315
-
316
  response = model.generate_content(prompt)
317
  parts = response.text.split('$$')
318
  if len(parts) == 2:
@@ -320,9 +333,7 @@ def generate_gemini_analysis(stock_name, stock_symbol, period, data):
320
  market_outlook = parts[1].strip()
321
  return dcc.Markdown(fundamental_analysis), dcc.Markdown(market_outlook)
322
  else:
323
- # Fallback for unexpected response format
324
  return dcc.Markdown("無法解析 Gemini 回應,請稍後再試。"), dcc.Markdown(response.text)
325
-
326
  except Exception as e:
327
  error_message = f"呼叫 Gemini API 時發生錯誤: {str(e)}"
328
  print(error_message)
@@ -330,6 +341,7 @@ def generate_gemini_analysis(stock_name, stock_symbol, period, data):
330
 
331
  # 建立 Dash 應用程式
332
  app = dash.Dash(__name__, suppress_callback_exceptions=True)
 
333
 
334
  try:
335
  print("正在初始化新聞情緒分析模型...")
@@ -384,7 +396,7 @@ app.layout = html.Div([
384
  html.Div([
385
  html.Div([
386
  html.Label("選擇股票:"),
387
- dcc.Dropdown(id='stock-dropdown', options=[{'label': name, 'value': symbol} for name, symbol in TAIWAN_STOCKS.items()], value='0050.TW', style={'margin-bottom': '10px'})
388
  ], style={'width': '30%', 'display': 'inline-block', 'vertical-align': 'top'}),
389
  html.Div([
390
  html.Label("時間範圍:"),
@@ -459,25 +471,22 @@ def update_taiex_prediction(predict_days):
459
  data = get_stock_data('^TWII', '2y')
460
  if data.empty: return html.Div("無法獲取台指期資料"), {}
461
 
462
- # === 呼叫 get_prediction 控制器,它會自動選擇模型 ===
463
  final_prediction = get_prediction(data, predict_days)
464
 
465
- if final_prediction is None: return html.Div("資料不足,無法進行預測"), {}
466
  current_price, last_date = data['Close'].iloc[-1], data.index[-1]
467
  predicted_price, change_pct, confidence = final_prediction['predicted_price'], final_prediction['change_pct'], final_prediction['confidence']
468
 
469
- prediction_paths = {1: [1], 5: [1, 5], 10: [1, 5, 10], 20: [1, 10, 20], 60: [1, 10, 20, 60]}
470
  intervals_to_predict = prediction_paths.get(predict_days, [predict_days])
471
  prediction_dates, prediction_prices = [last_date], [current_price]
472
 
473
  for days in intervals_to_predict:
474
- # === 迴圈內也使用統一的預測控制器 ===
475
  interim_prediction = get_prediction(data, days)
476
  if interim_prediction:
477
  prediction_dates.append(last_date + timedelta(days=days))
478
  prediction_prices.append(interim_prediction['predicted_price'])
479
 
480
- # (後續繪圖邏輯不變)
481
  color, arrow = ('red', '📈') if change_pct >= 0 else ('green', '📉')
482
  result_card = html.Div([
483
  html.H4(f"{predict_days}日後預測結果", style={'margin': '0 0 15px 0', 'color': 'white'}),
@@ -694,7 +703,7 @@ def update_analysis_text(selected_stock, period):
694
  print(f"從快取載入分析: {cache_key}")
695
  return cached_data['technical'], cached_data['fundamental'], cached_data['outlook']
696
 
697
- print(f"重新生成分析: {cache_key}")
698
  data = get_stock_data(selected_stock, period)
699
  stock_name = [name for name, symbol in TAIWAN_STOCKS.items() if symbol == selected_stock][0]
700
  if data.empty or len(data) < 20:
@@ -742,9 +751,6 @@ def update_pmi_chart(selected_stock):
742
  return fig
743
 
744
  def summarize_news_with_gemini(news_list: list) -> str:
745
- """
746
- 使用 Gemini API 將英文新聞標題列表摘要成一段繁體中文。
747
- """
748
  api_key = os.getenv("GEMINI_API_KEY")
749
  if not api_key:
750
  return "錯誤:找不到 GEMINI_API_KEY。請在 Hugging Face Secrets 中設定。"
@@ -752,22 +758,17 @@ def summarize_news_with_gemini(news_list: list) -> str:
752
  try:
753
  genai.configure(api_key=api_key)
754
  model = genai.GenerativeModel('gemini-1.5-flash')
755
-
756
  formatted_news = "\n".join([f"- {news}" for news in news_list])
757
-
758
  prompt = f"""
759
  請扮演一位專業的金融市場分析師。
760
  以下是幾則最新的英文財經新聞標題,請將它們整合成一段簡潔、流暢、約 200 字的繁體中文市場動態摘要,與利多哪些產業,利空哪些產業。
761
  提供3段重點,
762
  請專注於可能影響市場情緒和股價的關鍵資訊,並直接提供摘要內容,不要包含任何額外的問候語或說明。
763
-
764
  英文新聞標題如下:
765
  {formatted_news}
766
  """
767
-
768
  response = model.generate_content(prompt)
769
  return response.text
770
-
771
  except Exception as e:
772
  print(f"呼叫 Gemini API 時發生錯誤: {e}")
773
  return f"無法生成新聞摘要,請稍後再試。錯誤訊息:{e}"
@@ -792,7 +793,8 @@ def update_comparison_analysis(selected_stocks, period):
792
  normalized_prices = (data['Close'] / data['Close'].iloc[0]) * 100
793
  fig.add_trace(go.Scatter(x=data.index, y=normalized_prices, mode='lines', name=stock_name, line=dict(width=2)))
794
  total_return = ((data['Close'].iloc[-1] / data['Close'].iloc[0]) - 1) * 100
795
- volatility = data['Close'].pct_change().std() * np.sqrt(252) * 100
 
796
  comparison_data.append({'name': stock_name, 'return': total_return, 'volatility': volatility, 'current_price': data['Close'].iloc[-1]})
797
  fig.update_layout(title=f'股票績效比較 - {period}', xaxis_title='日期', yaxis_title='相對績效 (基期=100)', height=400, hovermode='x unified')
798
  if comparison_data:
 
1
+ # HUGING_FACE_V4.2(輕量AI版).py - 已整合 XGBoost 模型 (錯誤修正版)
2
 
3
  # 系統套件
4
  import os
 
21
  # 引用您組員的預測器程式
22
  from Bert_predict import BertPredictor
23
 
24
+ # 匯入 XGBoostModel 類別
25
  from model_predictor import XGBoostModel
26
  # ========================== 引用外部模組 END ==========================
27
 
28
  # ========================= 全域設定 START =========================
29
+ # 將開關設為 True 來啟用您的 XGBoost 模型
30
  USE_ADVANCED_MODEL = True
31
 
32
  # ========================= CACHE 設定 START =========================
 
36
  CACHE_DURATION_SECONDS = 8 * 60 * 60
37
  # ========================== CACHE 設定 END ==========================
38
 
39
+ # 在應用程式啟動時,預先載入 XGBoost 模型
40
  try:
41
  print("正在初始化 XGBoost 預測模型...")
42
  xgb_model = XGBoostModel(default_model='xgboost_model')
 
118
  change_pct = ((predicted_price - prices[-1]) / prices[-1]) * 100
119
  return {'predicted_price': predicted_price, 'change_pct': change_pct, 'confidence': max(0.6, 1 - volatility * 2)}
120
 
121
+ # 【修正 1】: 修正 XGBoost 模型的輸入資料
122
  def advanced_xgboost_predict(data, predict_days):
123
  """
124
  【進階模型橋接函式】
 
130
  return None
131
 
132
  # 1. 準備輸入資料
133
+ # 根據錯誤日誌,模型需要的特徵是 ['Open', 'High', 'Low', 'Volume']
134
+ feature_columns = ['Open', 'High', 'Low', 'Volume']
135
+
136
+ # 我們使用最新的資料點來進行未來預測
137
  input_df = data.tail(1)
138
 
139
+ # 確保輸入的 DataFrame 只包含模型需要的欄位
140
+ if not all(col in input_df.columns for col in feature_columns):
141
+ print(f"錯誤: 輸入資料缺少必要欄位。需要 {feature_columns}")
142
+ return None
143
+
144
+ # 篩選出模型需要的特徵欄位
145
+ input_df_filtered = input_df[feature_columns]
146
+
147
  try:
148
  # 2. 呼叫模型預測
149
+ predictions = xgb_model.predict('xgboost_model', input_df_filtered)
150
 
151
  # 3. 根據 predict_days 解析輸出
 
152
  day_to_key_map = {
153
+ 1: 'Close_t0_pred',
154
  5: 'Close_t5_pred',
155
  10: 'Close_t10_pred',
156
  20: 'Close_t20_pred',
157
+ 60: None
158
  }
159
 
 
160
  prediction_key = day_to_key_map.get(predict_days)
161
 
162
  if prediction_key is None or prediction_key not in predictions:
163
  print(f"警告: XGBoost 模型沒有提供 {predict_days} 天的預測結果。")
164
+ return None
165
 
166
  predicted_price = predictions[prediction_key]
167
  current_price = data['Close'].iloc[-1]
168
  change_pct = ((predicted_price - current_price) / current_price) * 100
169
 
170
  # 4. 包裝成主程式所需的格式
 
171
  return {
172
  'predicted_price': predicted_price,
173
  'change_pct': change_pct,
174
+ 'confidence': 0.95
175
  }
176
  except Exception as e:
177
  print(f"執行 XGBoost 預測時發生錯誤: {e}")
 
179
 
180
  def get_prediction(data, predict_days=5):
181
  """
182
+ 【模型預測控制器】
183
  根據 USE_ADVANCED_MODEL 的設定,呼叫對應的預測模型。
184
  """
185
  if USE_ADVANCED_MODEL:
186
  print(f"模式: 進階XGBoost模型 | 預測天期: {predict_days}天")
 
187
  prediction = advanced_xgboost_predict(data, predict_days)
 
188
  if prediction is not None:
189
  return prediction
190
  else:
191
  print("進階模型預測失敗或無對應天期,自動降級為簡易統計模型。")
192
 
 
193
  print(f"模式: 簡易統計模型 | 預測天期: {predict_days}天")
194
  return simple_statistical_predict(data, predict_days)
195
 
 
231
  df['ADX'] = df['DX'].ewm(com=13, adjust=False).mean()
232
  return df
233
 
234
+ # 【修正 2】: 修正 Volume Profile 計算中的 NaN 錯誤
235
  def calculate_volume_profile(df, num_bins=50):
236
  if df.empty or 'High' not in df.columns or 'Low' not in df.columns or 'Volume' not in df.columns: return None, None, None
237
+
238
+ # 結合高低價並移除可能存在的 NaN 值
239
  all_prices = np.concatenate([df['High'].values, df['Low'].values])
240
+ all_prices = all_prices[~np.isnan(all_prices)]
241
+
242
+ # 如果移除 NaN 後沒有數據,則直接返回
243
+ if all_prices.size == 0:
244
+ return None, None, None
245
+
246
  min_price, max_price = all_prices.min(), all_prices.max()
247
+
248
+ # 如果價格範圍無效,也返回
249
+ if min_price >= max_price:
250
+ return None, None, None
251
+
252
  price_for_volume = (df['High'] + df['Low'] + df['Close']) / 3
253
  df_vol_profile = df.copy()
254
  df_vol_profile['Price_Indicator'] = price_for_volume
255
+
256
+ # 確保用於計算權重的 Volume 也沒有 NaN
257
+ weights = df_vol_profile['Volume'].fillna(0).values
258
+
259
+ hist, bin_edges = np.histogram(df_vol_profile['Price_Indicator'].dropna(), bins=num_bins, range=(min_price, max_price), weights=weights)
260
  price_centers = (bin_edges[:-1] + bin_edges[1:]) / 2
261
  return bin_edges, hist, price_centers
262
 
 
290
  return pd.DataFrame()
291
 
292
  def generate_gemini_analysis(stock_name, stock_symbol, period, data):
 
 
 
293
  api_key = os.getenv("GEMINI_API_KEY")
294
  if not api_key:
295
  return "無法讀取 GEMINI API 金鑰", "請在系統環境變數中設定您的金鑰"
 
297
  try:
298
  genai.configure(api_key=api_key)
299
  model = genai.GenerativeModel('gemini-1.5-flash')
 
300
  price_change = ((data['Close'].iloc[-1] - data['Close'].iloc[0]) / data['Close'].iloc[0]) * 100
301
  rsi_current = data['RSI'].iloc[-1]
302
  macd_current = data['MACD'].iloc[-1]
 
306
  prompt = f"""
307
  請扮演一位專業、資深的台灣股市金融分析師。
308
  我將提供一檔台股的即時技術指標數據,請你基於這些數據,結合你對這家公司、其所在產業以及當前市場趨勢的理解,為我生成一段專業的「基本面分析」和一段「市場展望與投資建議」。
 
309
  **股票資訊:**
310
  - **公司名稱:** {stock_name} ({stock_symbol})
311
  - **分析期間:** 最近 {period}
 
313
  - **期間價格變動:** {price_change:+.2f}%
314
  - **目前 RSI 指標:** {rsi_current:.2f}
315
  - **目前 MACD 指標:** MACD線為 {macd_current:.3f}, 信號線為 {macd_signal_current:.3f}
 
316
  **你的任務:**
317
  1. **基本面分析 (約 150 字):**
318
  - 評論這家公司的產業地位、近期營運亮點或挑戰。
319
  - 提及任何可能影響其基本面的關鍵因素 (例如:財報、法說會、政策、供應鏈變化等)。
320
  - 請用專業、客觀的語氣撰寫。
 
321
  2. **市場展望與投資建議 (約 150 字):**
322
  - 基於上述所有資訊,提供對該股票的短期和中期市場展望。
323
  - 提出具體的投資建議,例如:適合何種類型的投資人、潛在的風險點。
324
  - 請直接提供分析內容,不要包含任何問候語。
 
325
  **輸出格式:**
326
  請嚴格按照以下格式回傳,使用"$$"作為兩個段落之間的分隔符:
327
  [基本面分析內容]$$[市場展望與投資建議內容]
328
  """
 
329
  response = model.generate_content(prompt)
330
  parts = response.text.split('$$')
331
  if len(parts) == 2:
 
333
  market_outlook = parts[1].strip()
334
  return dcc.Markdown(fundamental_analysis), dcc.Markdown(market_outlook)
335
  else:
 
336
  return dcc.Markdown("無法解析 Gemini 回應,請稍後再試。"), dcc.Markdown(response.text)
 
337
  except Exception as e:
338
  error_message = f"呼叫 Gemini API 時發生錯誤: {str(e)}"
339
  print(error_message)
 
341
 
342
  # 建立 Dash 應用程式
343
  app = dash.Dash(__name__, suppress_callback_exceptions=True)
344
+ server = app.server
345
 
346
  try:
347
  print("正在初始化新聞情緒分析模型...")
 
396
  html.Div([
397
  html.Div([
398
  html.Label("選擇股票:"),
399
+ dcc.Dropdown(id='stock-dropdown', options=[{'label': name, 'value': symbol} for name, symbol in TAIWAN_STOCKS.items()], value='2330.TW', style={'margin-bottom': '10px'})
400
  ], style={'width': '30%', 'display': 'inline-block', 'vertical-align': 'top'}),
401
  html.Div([
402
  html.Label("時間範圍:"),
 
471
  data = get_stock_data('^TWII', '2y')
472
  if data.empty: return html.Div("無法獲取台指期資料"), {}
473
 
 
474
  final_prediction = get_prediction(data, predict_days)
475
 
476
+ if final_prediction is None: return html.Div("資料不足或模型無法預測此天期"), {}
477
  current_price, last_date = data['Close'].iloc[-1], data.index[-1]
478
  predicted_price, change_pct, confidence = final_prediction['predicted_price'], final_prediction['change_pct'], final_prediction['confidence']
479
 
480
+ prediction_paths = {1: [1], 5: [1, 5], 10: [1, 5, 10], 20: [1, 10, 20]}
481
  intervals_to_predict = prediction_paths.get(predict_days, [predict_days])
482
  prediction_dates, prediction_prices = [last_date], [current_price]
483
 
484
  for days in intervals_to_predict:
 
485
  interim_prediction = get_prediction(data, days)
486
  if interim_prediction:
487
  prediction_dates.append(last_date + timedelta(days=days))
488
  prediction_prices.append(interim_prediction['predicted_price'])
489
 
 
490
  color, arrow = ('red', '📈') if change_pct >= 0 else ('green', '📉')
491
  result_card = html.Div([
492
  html.H4(f"{predict_days}日後預測結果", style={'margin': '0 0 15px 0', 'color': 'white'}),
 
703
  print(f"從快取載入分析: {cache_key}")
704
  return cached_data['technical'], cached_data['fundamental'], cached_data['outlook']
705
 
706
+ print(f"重新生成分析: {selected_stock}-{period}")
707
  data = get_stock_data(selected_stock, period)
708
  stock_name = [name for name, symbol in TAIWAN_STOCKS.items() if symbol == selected_stock][0]
709
  if data.empty or len(data) < 20:
 
751
  return fig
752
 
753
  def summarize_news_with_gemini(news_list: list) -> str:
 
 
 
754
  api_key = os.getenv("GEMINI_API_KEY")
755
  if not api_key:
756
  return "錯誤:找不到 GEMINI_API_KEY。請在 Hugging Face Secrets 中設定。"
 
758
  try:
759
  genai.configure(api_key=api_key)
760
  model = genai.GenerativeModel('gemini-1.5-flash')
 
761
  formatted_news = "\n".join([f"- {news}" for news in news_list])
 
762
  prompt = f"""
763
  請扮演一位專業的金融市場分析師。
764
  以下是幾則最新的英文財經新聞標題,請將它們整合成一段簡潔、流暢、約 200 字的繁體中文市場動態摘要,與利多哪些產業,利空哪些產業。
765
  提供3段重點,
766
  請專注於可能影響市場情緒和股價的關鍵資訊,並直接提供摘要內容,不要包含任何額外的問候語或說明。
 
767
  英文新聞標題如下:
768
  {formatted_news}
769
  """
 
770
  response = model.generate_content(prompt)
771
  return response.text
 
772
  except Exception as e:
773
  print(f"呼叫 Gemini API 時發生錯誤: {e}")
774
  return f"無法生成新聞摘要,請稍後再試。錯誤訊息:{e}"
 
793
  normalized_prices = (data['Close'] / data['Close'].iloc[0]) * 100
794
  fig.add_trace(go.Scatter(x=data.index, y=normalized_prices, mode='lines', name=stock_name, line=dict(width=2)))
795
  total_return = ((data['Close'].iloc[-1] / data['Close'].iloc[0]) - 1) * 100
796
+ # 【修正 3】: 修正 FutureWarning 警告
797
+ volatility = data['Close'].pct_change(fill_method=None).std() * np.sqrt(252) * 100
798
  comparison_data.append({'name': stock_name, 'return': total_return, 'volatility': volatility, 'current_price': data['Close'].iloc[-1]})
799
  fig.update_layout(title=f'股票績效比較 - {period}', xaxis_title='日期', yaxis_title='相對績效 (基期=100)', height=400, hovermode='x unified')
800
  if comparison_data: