AlanRex commited on
Commit
9268d6e
·
verified ·
1 Parent(s): 16e7c64

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +138 -147
app.py CHANGED
@@ -242,7 +242,7 @@ def simple_statistical_predict(data, predict_days=5):
242
 
243
  def advanced_xgboost_predict(predict_days=5):
244
  """
245
- 【進階模型】使用 XGBoost 模型進行預測
246
  """
247
  try:
248
  print(f"開始使用 XGBoost 模型進行 {predict_days} 天預測...")
@@ -259,12 +259,6 @@ def advanced_xgboost_predict(predict_days=5):
259
  # 計算技術指標
260
  taiex_data = calculate_technical_indicators(taiex_data)
261
 
262
- # 獲取美股市場數據
263
- us_market = get_us_market_data()
264
-
265
- # 獲取匯率數據
266
- exchange_rate = get_exchange_rate()
267
-
268
  # 獲取新聞情緒分數
269
  try:
270
  if predictor is not None:
@@ -279,160 +273,92 @@ def advanced_xgboost_predict(predict_days=5):
279
  # 準備特徵數據 (使用最新的數據點)
280
  latest_data = taiex_data.iloc[-1]
281
 
282
- # 建立特徵向量 (按照訓練數據記錄的順序)
283
- # 先檢查每個技術指標是否存在並記錄狀態
284
  tech_indicators_status = {}
285
 
286
- # RSI 檢查
287
- rsi_value = latest_data['RSI'] if not pd.isna(latest_data['RSI']) else 50
288
- tech_indicators_status['RSI'] = {
289
- 'value': rsi_value,
290
- 'is_real': not pd.isna(latest_data['RSI']),
291
- 'source': 'calculated' if not pd.isna(latest_data['RSI']) else 'default'
 
 
292
  }
293
 
294
- # MACD 相關檢查
295
- macd_value = latest_data['MACD'] if not pd.isna(latest_data['MACD']) else 0
296
- macd_signal_value = latest_data['MACD_Signal'] if not pd.isna(latest_data['MACD_Signal']) else 0
297
- macd_hist_value = latest_data['MACD_Histogram'] if not pd.isna(latest_data['MACD_Histogram']) else 0
298
-
299
- tech_indicators_status['MACD'] = {
300
- 'value': macd_value,
301
- 'is_real': not pd.isna(latest_data['MACD']),
302
- 'source': 'calculated' if not pd.isna(latest_data['MACD']) else 'default'
303
- }
304
- tech_indicators_status['MACD_Signal'] = {
305
- 'value': macd_signal_value,
306
- 'is_real': not pd.isna(latest_data['MACD_Signal']),
307
- 'source': 'calculated' if not pd.isna(latest_data['MACD_Signal']) else 'default'
308
- }
309
- tech_indicators_status['MACD_Histogram'] = {
310
- 'value': macd_hist_value,
311
- 'is_real': not pd.isna(latest_data['MACD_Histogram']),
312
- 'source': 'calculated' if not pd.isna(latest_data['MACD_Histogram']) else 'default'
313
- }
314
-
315
- # KD 指標檢查
316
- k_value = latest_data['K'] if not pd.isna(latest_data['K']) else 50
317
- d_value = latest_data['D'] if not pd.isna(latest_data['D']) else 50
318
-
319
- tech_indicators_status['K'] = {
320
- 'value': k_value,
321
- 'is_real': not pd.isna(latest_data['K']),
322
- 'source': 'calculated' if not pd.isna(latest_data['K']) else 'default'
323
- }
324
- tech_indicators_status['D'] = {
325
- 'value': d_value,
326
- 'is_real': not pd.isna(latest_data['D']),
327
- 'source': 'calculated' if not pd.isna(latest_data['D']) else 'default'
328
- }
329
-
330
- # DMI 指標檢查
331
- plus_di_value = latest_data['+DI'] if not pd.isna(latest_data['+DI']) else 25
332
- minus_di_value = latest_data['-DI'] if not pd.isna(latest_data['-DI']) else 25
333
- adx_value = latest_data['ADX'] if not pd.isna(latest_data['ADX']) else 25
334
-
335
- tech_indicators_status['+DI'] = {
336
- 'value': plus_di_value,
337
- 'is_real': not pd.isna(latest_data['+DI']),
338
- 'source': 'calculated' if not pd.isna(latest_data['+DI']) else 'default'
339
- }
340
- tech_indicators_status['-DI'] = {
341
- 'value': minus_di_value,
342
- 'is_real': not pd.isna(latest_data['-DI']),
343
- 'source': 'calculated' if not pd.isna(latest_data['-DI']) else 'default'
344
- }
345
- tech_indicators_status['ADX'] = {
346
- 'value': adx_value,
347
- 'is_real': not pd.isna(latest_data['ADX']),
348
- 'source': 'calculated' if not pd.isna(latest_data['ADX']) else 'default'
349
- }
350
 
351
- # 建立特徵向量
352
  features_list = [
353
- latest_data['Close'], # close
354
- latest_data['Volume'], # volume
355
- exchange_rate, # rate
356
- us_market['DJI'], # DJI
357
- us_market['NAS'], # NAS
358
- us_market['SOX'], # SOX
359
- us_market['S&P_500'], # S&P_500
360
- us_market['TSM_ADR'], # TSM_ADR
361
- sentiment_score_raw, # NEWS (使用原始 sentiment_score_raw)
362
- rsi_value, # RSI
363
- macd_value, # MACD
364
- macd_signal_value, # MACDsign
365
- macd_hist_value, # MACDvol
366
- k_value, # K
367
- d_value, # D
368
- plus_di_value, # +DI
369
- minus_di_value, # -DI
370
- adx_value, # ADX
371
- 15, # business_climate (手動填入值)
372
- 46.7 # PMI (手動填入值)
373
  ]
374
 
375
- # 轉換為 DataFrame (XGBoost 模型期望的格式)
376
  column_names = [
377
- 'close', 'volume', 'rate', 'DJI', 'NAS', 'SOX', 'S&P_500', 'TSM_ADR',
378
- 'NEWS', 'RSI', 'MACD', 'MACDsign', 'MACDvol', 'K', 'D',
379
- '+DI', '-DI', 'ADX', 'business_climate', 'PMI'
380
  ]
381
 
 
382
  input_df = pd.DataFrame([features_list], columns=column_names)
383
 
384
  # 詳細的資料驗證日誌
385
  print("=" * 50)
386
- print("XGBoost 模型輸入特徵詳細檢查報告")
387
  print("=" * 50)
388
 
389
- # 基本市場數據
390
- print("📊 基本市場數據:")
391
- print(f" 收盤價 (close): {latest_data['Close']:.2f}")
392
- print(f" 成交量 (volume): {latest_data['Volume']:,.0f}")
393
- print(f" 匯率 (rate): {exchange_rate:.4f}")
394
-
395
- # 美股指數
396
- print("\n🇺🇸 美股指數數據:")
397
- for key, value in us_market.items():
398
- status = "✅ 正常" if value > 0 else "⚠️ 可能異常(=0)"
399
- print(f" {key}: {value:.2f} {status}")
400
-
401
- # 新聞情緒
402
- print(f"\n📰 新聞情緒 (NEWS): {sentiment_score_raw:.6f}")
403
  if sentiment_score_raw == 0:
404
- print(" ⚠️ 新聞情緒分數為0,可能無新聞數據")
405
  else:
406
- print(" 新聞情緒分數正常")
407
 
408
  # 技術指標詳細狀態
409
- print("\n📈 技術指標狀態:")
410
- for indicator, status in tech_indicators_status.items():
411
- status_symbol = "✅" if status['is_real'] else "⚠️"
412
- source_info = "實際計算值" if status['is_real'] else "預設替代值"
413
- print(f" {indicator}: {status['value']:.4f} {status_symbol} ({source_info})")
414
-
415
- # 手動填入數據
416
- print("\n🔧 手動填入數據:")
417
- print(f" business_climate: 15 ✅")
418
- print(f" PMI: 46.7 ✅")
419
 
420
- # 統計資料完整性
421
  real_indicators = sum(1 for status in tech_indicators_status.values() if status['is_real'])
422
  total_indicators = len(tech_indicators_status)
423
  completeness = (real_indicators / total_indicators) * 100
424
 
425
- print(f"\n📋 技術指標完整性統計:")
426
  print(f" 實際計算指標: {real_indicators}/{total_indicators} ({completeness:.1f}%)")
427
- if completeness < 80:
428
- print(" ⚠️ 警告:超過20%的技術指標使用預設值,可能影響預測準確性")
429
  else:
430
- print(" ✅ 技術指標完整性良好")
431
 
432
- # 顯示完整輸入向量
433
- print(f"\n🔢 完整特徵向量 (共{len(features_list)}個特徵):")
434
  for i, (name, value) in enumerate(zip(column_names, features_list)):
435
- print(f" [{i:2d}] {name:15s}: {value:10.4f}")
436
 
437
  print("=" * 50)
438
 
@@ -453,7 +379,7 @@ def advanced_xgboost_predict(predict_days=5):
453
  pred_key = pred_mapping[closest_day]
454
 
455
  predicted_price = predictions[pred_key]
456
- current_price = features_list[0] # close price
457
  change_pct = ((predicted_price - current_price) / current_price) * 100
458
 
459
  print(f"XGBoost 預測完成:")
@@ -461,6 +387,7 @@ def advanced_xgboost_predict(predict_days=5):
461
  print(f"- 當前價格: {current_price:.2f}")
462
  print(f"- 預測價格: {predicted_price:.2f}")
463
  print(f"- 預測變化: {change_pct:+.2f}%")
 
464
 
465
  return {
466
  'predicted_price': predicted_price,
@@ -619,12 +546,12 @@ def generate_gemini_analysis(stock_name, stock_symbol, period, data):
619
  **你的任務:**
620
  1. **基本面分析 (約 150 字):**
621
  - 評論這家公司的產業地位、近期營運亮點或挑戰。
622
- - 提及任何可能影響其基本面的關鍵因素 (例如:最近公告財報日期、法說會日期、最近一次的EPS、最近一個月的MOM是否增長、政策、供應鏈變化等)。
623
  - 請用專業、客觀的語氣撰寫。
624
 
625
  2. **市場展望與投資建議 (約 150 字):**
626
  - 基於上述所有資訊,提供對該股票的短期和中期市場展望。
627
- - 提出具體的投資建議,例如:適合何種類型的投資人(價值投資、波段投資、動能投資)、潛在的風險點。
628
  - 請直接提供分析內容,不要包含任何問候語。
629
 
630
  **輸出格式:**
@@ -953,25 +880,89 @@ def update_industry_analysis(selected_stock):
953
  '月報酬率(%)': return_pct,
954
  '絕對波動': abs(return_pct)
955
  })
 
956
  if not performance_data:
957
  fig = go.Figure().add_annotation(text="無法計算產業資料", showarrow=False)
958
- fig.update_layout(title="近一月市場波動最大標的", height=400)
959
  return fig
 
960
  df_performance = pd.DataFrame(performance_data)
961
- df_top_movers = df_performance.sort_values(by='絕對波動', ascending=False).head(10)
962
- fig = px.pie(
963
- df_top_movers,
964
- values='絕對波動',
965
- names='股票',
966
- title='近一月市場波動最大 Top 10 標的',
967
- hover_data={'月報酬率(%)': ':.2f'}
 
 
 
 
 
 
 
 
968
  )
969
- fig.update_traces(
970
- textposition='inside',
971
- textinfo='percent+label',
972
- hovertemplate="<b>%{label}</b><br>月報酬率: %{customdata[0]:.2f}%<extra></extra>"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
973
  )
974
- fig.update_layout(height=400, showlegend=False)
975
  return fig
976
 
977
  @app.callback(
 
242
 
243
  def advanced_xgboost_predict(predict_days=5):
244
  """
245
+ 【進階模型】使用 XGBoost 模型進行預測 - 8個特徵版本
246
  """
247
  try:
248
  print(f"開始使用 XGBoost 模型進行 {predict_days} 天預測...")
 
259
  # 計算技術指標
260
  taiex_data = calculate_technical_indicators(taiex_data)
261
 
 
 
 
 
 
 
262
  # 獲取新聞情緒分數
263
  try:
264
  if predictor is not None:
 
273
  # 準備特徵數據 (使用最新的數據點)
274
  latest_data = taiex_data.iloc[-1]
275
 
276
+ # 技術指標檢查和狀態記錄 - 只處理需要的8個特徵
 
277
  tech_indicators_status = {}
278
 
279
+ # 檢查各技術指標並記錄狀態 - 移除MACD相關指標
280
+ indicators_map = {
281
+ 'K': (latest_data['K'], 50),
282
+ 'D': (latest_data['D'], 50),
283
+ '+DI': (latest_data['+DI'], 25),
284
+ '-DI': (latest_data['-DI'], 25),
285
+ 'ADX': (latest_data['ADX'], 25),
286
+ 'RSI': (latest_data['RSI'], 50),
287
  }
288
 
289
+ processed_values = {}
290
+ for indicator, (value, default) in indicators_map.items():
291
+ if pd.isna(value):
292
+ processed_values[indicator] = default
293
+ tech_indicators_status[indicator] = {
294
+ 'value': default,
295
+ 'is_real': False,
296
+ 'source': 'default'
297
+ }
298
+ else:
299
+ processed_values[indicator] = value
300
+ tech_indicators_status[indicator] = {
301
+ 'value': value,
302
+ 'is_real': True,
303
+ 'source': 'calculated'
304
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
305
 
306
+ # 建立8個特徵的向量 (按您指定的順序)
307
  features_list = [
308
+ latest_data['Close'], # close
309
+ sentiment_score_raw, # NEWS
310
+ processed_values['K'], # K
311
+ processed_values['D'], # D
312
+ processed_values['+DI'], # +DI
313
+ processed_values['-DI'], # -DI
314
+ processed_values['ADX'], # ADX
315
+ processed_values['RSI'], # RSI
 
 
 
 
 
 
 
 
 
 
 
 
316
  ]
317
 
318
+ # 對應的欄位名稱 - 必須與模型訓練時的順序完全一致
319
  column_names = [
320
+ 'close', 'NEWS', 'K', 'D', '+DI', '-DI', 'ADX', 'RSI'
 
 
321
  ]
322
 
323
+ # 轉換為 DataFrame (XGBoost 模型期望的格式)
324
  input_df = pd.DataFrame([features_list], columns=column_names)
325
 
326
  # 詳細的資料驗證日誌
327
  print("=" * 50)
328
+ print("XGBoost 模型輸入特徵檢查報告 (8特徵版)")
329
  print("=" * 50)
330
 
331
+ # 基本資料
332
+ print(f"收盤價 (close): {latest_data['Close']:.2f}")
333
+ print(f"新聞情緒 (NEWS): {sentiment_score_raw:.6f}")
 
 
 
 
 
 
 
 
 
 
 
334
  if sentiment_score_raw == 0:
335
+ print(" 警告: 新聞情緒分數為0,可能無新聞數據")
336
  else:
337
+ print(" 新聞情緒分數正常")
338
 
339
  # 技術指標詳細狀態
340
+ print("\n技術指標狀態:")
341
+ for indicator in ['K', 'D', '+DI', '-DI', 'ADX', 'RSI']:
342
+ status = tech_indicators_status[indicator]
343
+ status_symbol = "正常" if status['is_real'] else "預設值"
344
+ print(f" {indicator:6s}: {status['value']:8.4f} ({status_symbol})")
 
 
 
 
 
345
 
346
+ # 統計完整性
347
  real_indicators = sum(1 for status in tech_indicators_status.values() if status['is_real'])
348
  total_indicators = len(tech_indicators_status)
349
  completeness = (real_indicators / total_indicators) * 100
350
 
351
+ print(f"\n技術指標完整性:")
352
  print(f" 實際計算指標: {real_indicators}/{total_indicators} ({completeness:.1f}%)")
353
+ if completeness < 70:
354
+ print(" 警告: 超過30%的指標使用預設值,可能影響預測準確性")
355
  else:
356
+ print(" 指標完整性良好")
357
 
358
+ # 顯示完整特徵向量
359
+ print(f"\n完整特徵向量 (共{len(features_list)}個特徵):")
360
  for i, (name, value) in enumerate(zip(column_names, features_list)):
361
+ print(f" [{i:1d}] {name:6s}: {value:12.6f}")
362
 
363
  print("=" * 50)
364
 
 
379
  pred_key = pred_mapping[closest_day]
380
 
381
  predicted_price = predictions[pred_key]
382
+ current_price = latest_data['Close']
383
  change_pct = ((predicted_price - current_price) / current_price) * 100
384
 
385
  print(f"XGBoost 預測完成:")
 
387
  print(f"- 當前價格: {current_price:.2f}")
388
  print(f"- 預測價格: {predicted_price:.2f}")
389
  print(f"- 預測變化: {change_pct:+.2f}%")
390
+ print(f"- 使用特徵數: {len(features_list)} 個")
391
 
392
  return {
393
  'predicted_price': predicted_price,
 
546
  **你的任務:**
547
  1. **基本面分析 (約 150 字):**
548
  - 評論這家公司的產業地位、近期營運亮點或挑戰。
549
+ - 提及任何可能影響其基本面的關鍵因素 (例如:財報、法說會、政策、供應鏈變化等)。
550
  - 請用專業、客觀的語氣撰寫。
551
 
552
  2. **市場展望與投資建議 (約 150 字):**
553
  - 基於上述所有資訊,提供對該股票的短期和中期市場展望。
554
+ - 提出具體的投資建議,例如:適合何種類型的投資人、潛在的風險點。
555
  - 請直接提供分析內容,不要包含任何問候語。
556
 
557
  **輸出格式:**
 
880
  '月報酬率(%)': return_pct,
881
  '絕對波動': abs(return_pct)
882
  })
883
+
884
  if not performance_data:
885
  fig = go.Figure().add_annotation(text="無法計算產業資料", showarrow=False)
886
+ fig.update_layout(title="近一月市場表現分析", height=400)
887
  return fig
888
+
889
  df_performance = pd.DataFrame(performance_data)
890
+
891
+ # 分離漲跌幅數據
892
+ gainers = df_performance[df_performance['月報酬率(%)'] > 0].copy()
893
+ losers = df_performance[df_performance['月報酬率(%)'] < 0].copy()
894
+
895
+ # 按報酬率排序並取前5名
896
+ top_gainers = gainers.sort_values(by='月報酬率(%)', ascending=False).head(5)
897
+ top_losers = losers.sort_values(by='月報酬率(%)', ascending=True).head(5)
898
+
899
+ # 創建子圖布局 - 1行2列
900
+ fig = make_subplots(
901
+ rows=1, cols=2,
902
+ specs=[[{"type": "pie"}, {"type": "pie"}]],
903
+ subplot_titles=('📈 近一月漲幅排行 Top 5', '📉 近一月跌幅排行 Top 5'),
904
+ horizontal_spacing=0.1
905
  )
906
+
907
+ # 如果有上漲的股票,添加漲幅圓餅圖
908
+ if not top_gainers.empty:
909
+ fig.add_trace(go.Pie(
910
+ labels=top_gainers['股票'],
911
+ values=top_gainers['月報酬率(%)'],
912
+ name="漲幅",
913
+ textinfo='label+percent',
914
+ textposition='inside',
915
+ marker=dict(colors=['#FF6B6B', '#FF8E53', '#FF6B9D', '#C44569', '#F8B500']),
916
+ hovertemplate="<b>%{label}</b><br>漲幅: +%{value:.1f}%<extra></extra>",
917
+ textfont=dict(size=12)
918
+ ), row=1, col=1)
919
+ else:
920
+ # 如果沒有上漲股票,顯示提示
921
+ fig.add_annotation(
922
+ text="本月無上漲股票",
923
+ x=0.25, y=0.5,
924
+ showarrow=False,
925
+ font=dict(size=16, color="gray")
926
+ )
927
+
928
+ # 如果有下跌的股票,添加跌幅圓餅圖(使用絕對值)
929
+ if not top_losers.empty:
930
+ fig.add_trace(go.Pie(
931
+ labels=top_losers['股票'],
932
+ values=abs(top_losers['月報酬率(%)']), # 使用絕對值讓圓餅圖正常顯示
933
+ name="跌幅",
934
+ textinfo='label+percent',
935
+ textposition='inside',
936
+ marker=dict(colors=['#20BF6B', '#26DE81', '#2BCBBA', '#45AAF2', '#4834D4']),
937
+ hovertemplate="<b>%{label}</b><br>跌幅: %{customdata:.1f}%<extra></extra>",
938
+ customdata=top_losers['月報酬率(%)'], # 顯示實際的負值
939
+ textfont=dict(size=12)
940
+ ), row=1, col=2)
941
+ else:
942
+ # 如果沒有下跌股票,顯示提示
943
+ fig.add_annotation(
944
+ text="本月無下跌股票",
945
+ x=0.75, y=0.5,
946
+ showarrow=False,
947
+ font=dict(size=16, color="gray")
948
+ )
949
+
950
+ # 更新布局
951
+ fig.update_layout(
952
+ title_text="近一月市場表現分析 - 漲跌分佈",
953
+ height=500,
954
+ showlegend=False,
955
+ font=dict(size=11),
956
+ title_font_size=16,
957
+ annotations=[
958
+ dict(text=f"統計範圍:{len(performance_data)}檔股票",
959
+ x=0.5, y=-0.1,
960
+ showarrow=False,
961
+ xanchor="center",
962
+ font=dict(size=12, color="gray"))
963
+ ]
964
  )
965
+
966
  return fig
967
 
968
  @app.callback(