AlanRex commited on
Commit
16e7c64
·
verified ·
1 Parent(s): 1519e8a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +144 -143
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 模型進行預測 - 簡化版 (11個特徵)
246
  """
247
  try:
248
  print(f"開始使用 XGBoost 模型進行 {predict_days} 天預測...")
@@ -259,6 +259,12 @@ def advanced_xgboost_predict(predict_days=5):
259
  # 計算技術指標
260
  taiex_data = calculate_technical_indicators(taiex_data)
261
 
 
 
 
 
 
 
262
  # 獲取新聞情緒分數
263
  try:
264
  if predictor is not None:
@@ -273,100 +279,160 @@ def advanced_xgboost_predict(predict_days=5):
273
  # 準備特徵數據 (使用最新的數據點)
274
  latest_data = taiex_data.iloc[-1]
275
 
276
- # 技術指標檢查和狀態記錄
 
277
  tech_indicators_status = {}
278
 
279
- # 檢查各技術指標並記錄狀態
280
- indicators_map = {
281
- 'RSI': (latest_data['RSI'], 50),
282
- 'MACD': (latest_data['MACD'], 0),
283
- 'MACDsign': (latest_data['MACD_Signal'], 0),
284
- 'MACDvol': (latest_data['MACD_Histogram'], 0),
285
- 'K': (latest_data['K'], 50),
286
- 'D': (latest_data['D'], 50),
287
- '+DI': (latest_data['+DI'], 25),
288
- '-DI': (latest_data['-DI'], 25),
289
- 'ADX': (latest_data['ADX'], 25),
290
  }
291
 
292
- processed_values = {}
293
- for indicator, (value, default) in indicators_map.items():
294
- if pd.isna(value):
295
- processed_values[indicator] = default
296
- tech_indicators_status[indicator] = {
297
- 'value': default,
298
- 'is_real': False,
299
- 'source': 'default'
300
- }
301
- else:
302
- processed_values[indicator] = value
303
- tech_indicators_status[indicator] = {
304
- 'value': value,
305
- 'is_real': True,
306
- 'source': 'calculated'
307
- }
308
 
309
- # 建立簡化的特徵向量 (按您指定的順序)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
310
  features_list = [
311
- latest_data['Close'], # close
312
- sentiment_score_raw, # NEWS
313
- processed_values['MACD'], # MACD
314
- processed_values['MACDsign'], # MACDsign
315
- processed_values['MACDvol'], # MACDvol
316
- processed_values['K'], # K
317
- processed_values['D'], # D
318
- processed_values['+DI'], # +DI
319
- processed_values['-DI'], # -DI
320
- processed_values['ADX'], # ADX
321
- processed_values['RSI'], # RSI
 
 
 
 
 
 
 
 
 
322
  ]
323
 
324
- # 對應的欄位名稱
325
- column_names = ['MACD', 'MACDsign', 'MACDvol',
326
- 'close', 'NEWS',
327
- 'K', 'D', '+DI', '-DI', 'ADX', 'RSI'
 
328
  ]
329
 
330
- # 轉換為 DataFrame (XGBoost 模型期望的格式)
331
  input_df = pd.DataFrame([features_list], columns=column_names)
332
 
333
  # 詳細的資料驗證日誌
334
  print("=" * 50)
335
- print("XGBoost 簡化模型輸入特徵檢查報告")
336
  print("=" * 50)
337
 
338
- # 基本資料
339
- print(f"收盤價 (close): {latest_data['Close']:.2f}")
340
- print(f"新聞情緒 (NEWS): {sentiment_score_raw:.6f}")
 
 
 
 
 
 
 
 
 
 
 
341
  if sentiment_score_raw == 0:
342
  print(" ⚠️ 新聞情緒分數為0,可能無新聞數據")
343
  else:
344
  print(" ✅ 新聞情緒分數正常")
345
 
346
- # 技術指標詳細狀態
347
  print("\n📈 技術指標狀態:")
348
- for indicator in ['MACD', 'MACDsign', 'MACDvol', 'K', 'D', '+DI', '-DI', 'ADX', 'RSI']:
349
- status = tech_indicators_status[indicator]
350
  status_symbol = "✅" if status['is_real'] else "⚠️"
351
- source_info = "實際計算" if status['is_real'] else "預設值"
352
- print(f" {indicator:10s}: {status['value']:8.4f} {status_symbol} ({source_info})")
 
 
 
 
 
353
 
354
- # 統計完整性
355
  real_indicators = sum(1 for status in tech_indicators_status.values() if status['is_real'])
356
  total_indicators = len(tech_indicators_status)
357
  completeness = (real_indicators / total_indicators) * 100
358
 
359
- print(f"\n📋 技術指標完整性:")
360
  print(f" 實際計算指標: {real_indicators}/{total_indicators} ({completeness:.1f}%)")
361
- if completeness < 70:
362
- print(" ⚠️ 警告:超過30%的指標使用預設值,可能影響預測準確性")
363
  else:
364
- print(" ✅ 指標完整性良好")
365
 
366
- # 顯示完整特徵向量
367
  print(f"\n🔢 完整特徵向量 (共{len(features_list)}個特徵):")
368
  for i, (name, value) in enumerate(zip(column_names, features_list)):
369
- print(f" [{i:2d}] {name:10s}: {value:12.6f}")
370
 
371
  print("=" * 50)
372
 
@@ -387,7 +453,7 @@ def advanced_xgboost_predict(predict_days=5):
387
  pred_key = pred_mapping[closest_day]
388
 
389
  predicted_price = predictions[pred_key]
390
- current_price = latest_data['Close']
391
  change_pct = ((predicted_price - current_price) / current_price) * 100
392
 
393
  print(f"XGBoost 預測完成:")
@@ -395,7 +461,6 @@ def advanced_xgboost_predict(predict_days=5):
395
  print(f"- 當前價格: {current_price:.2f}")
396
  print(f"- 預測價格: {predicted_price:.2f}")
397
  print(f"- 預測變化: {change_pct:+.2f}%")
398
- print(f"- 使用特徵數: {len(features_list)} 個")
399
 
400
  return {
401
  'predicted_price': predicted_price,
@@ -554,12 +619,12 @@ def generate_gemini_analysis(stock_name, stock_symbol, period, data):
554
  **你的任務:**
555
  1. **基本面分析 (約 150 字):**
556
  - 評論這家公司的產業地位、近期營運亮點或挑戰。
557
- - 提及任何可能影響其基本面的關鍵因素 (例如:財報、法說會、政策、供應鏈變化等)。
558
  - 請用專業、客觀的語氣撰寫。
559
 
560
  2. **市場展望與投資建議 (約 150 字):**
561
  - 基於上述所有資訊,提供對該股票的短期和中期市場展望。
562
- - 提出具體的投資建議,例如:適合何種類型的投資人、潛在的風險點。
563
  - 請直接提供分析內容,不要包含任何問候語。
564
 
565
  **輸出格式:**
@@ -888,89 +953,25 @@ def update_industry_analysis(selected_stock):
888
  '月報酬率(%)': return_pct,
889
  '絕對波動': abs(return_pct)
890
  })
891
-
892
  if not performance_data:
893
  fig = go.Figure().add_annotation(text="無法計算產業資料", showarrow=False)
894
- fig.update_layout(title="近一月市場表現分析", height=400)
895
  return fig
896
-
897
  df_performance = pd.DataFrame(performance_data)
898
-
899
- # 分離漲跌幅數據
900
- gainers = df_performance[df_performance['月報酬率(%)'] > 0].copy()
901
- losers = df_performance[df_performance['月報酬率(%)'] < 0].copy()
902
-
903
- # 按報酬率排序並取前5名
904
- top_gainers = gainers.sort_values(by='月報酬率(%)', ascending=False).head(5)
905
- top_losers = losers.sort_values(by='月報酬率(%)', ascending=True).head(5)
906
-
907
- # 創建子圖布局 - 1行2列
908
- fig = make_subplots(
909
- rows=1, cols=2,
910
- specs=[[{"type": "pie"}, {"type": "pie"}]],
911
- subplot_titles=('📈 近一月漲幅排行 Top 5', '📉 近一月跌幅排行 Top 5'),
912
- horizontal_spacing=0.1
913
  )
914
-
915
- # 如果有上漲的股票,添加漲幅圓餅圖
916
- if not top_gainers.empty:
917
- fig.add_trace(go.Pie(
918
- labels=top_gainers['股票'],
919
- values=top_gainers['月報酬率(%)'],
920
- name="漲幅",
921
- textinfo='label+percent',
922
- textposition='inside',
923
- marker=dict(colors=['#FF6B6B', '#FF8E53', '#FF6B9D', '#C44569', '#F8B500']),
924
- hovertemplate="<b>%{label}</b><br>漲幅: +%{value:.1f}%<extra></extra>",
925
- textfont=dict(size=12)
926
- ), row=1, col=1)
927
- else:
928
- # 如果沒有上漲股票,顯示提示
929
- fig.add_annotation(
930
- text="本月無上漲股票",
931
- x=0.25, y=0.5,
932
- showarrow=False,
933
- font=dict(size=16, color="gray")
934
- )
935
-
936
- # 如果有下跌的股票,添加跌幅圓餅圖(使用絕對值)
937
- if not top_losers.empty:
938
- fig.add_trace(go.Pie(
939
- labels=top_losers['股票'],
940
- values=abs(top_losers['月報酬率(%)']), # 使用絕對值讓圓餅圖正常顯示
941
- name="跌幅",
942
- textinfo='label+percent',
943
- textposition='inside',
944
- marker=dict(colors=['#20BF6B', '#26DE81', '#2BCBBA', '#45AAF2', '#4834D4']),
945
- hovertemplate="<b>%{label}</b><br>跌幅: %{customdata:.1f}%<extra></extra>",
946
- customdata=top_losers['月報酬率(%)'], # 顯示實際的負值
947
- textfont=dict(size=12)
948
- ), row=1, col=2)
949
- else:
950
- # 如果沒有下跌股票,顯示提示
951
- fig.add_annotation(
952
- text="本月無下跌股票",
953
- x=0.75, y=0.5,
954
- showarrow=False,
955
- font=dict(size=16, color="gray")
956
- )
957
-
958
- # 更新布局
959
- fig.update_layout(
960
- title_text="近一月市場表現分析 - 漲跌分佈",
961
- height=500,
962
- showlegend=False,
963
- font=dict(size=11),
964
- title_font_size=16,
965
- annotations=[
966
- dict(text=f"統計範圍:{len(performance_data)}檔股票",
967
- x=0.5, y=-0.1,
968
- showarrow=False,
969
- xanchor="center",
970
- font=dict(size=12, color="gray"))
971
- ]
972
  )
973
-
974
  return fig
975
 
976
  @app.callback(
 
242
 
243
  def advanced_xgboost_predict(predict_days=5):
244
  """
245
+ 【進階模型】使用 XGBoost 模型進行預測
246
  """
247
  try:
248
  print(f"開始使用 XGBoost 模型進行 {predict_days} 天預測...")
 
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
  # 準備特徵數據 (使用最新的數據點)
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
  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
  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
  **你的任務:**
620
  1. **基本面分析 (約 150 字):**
621
  - 評論這家公司的產業地位、近期營運亮點或挑戰。
622
+ - 提及任何可能影響其基本面的關鍵因素 (例如:最近公告財報日期、法說會日期、最近一次的EPS、最近一個月的MOM是否增長、政策、供應鏈變化等)。
623
  - 請用專業、客觀的語氣撰寫。
624
 
625
  2. **市場展望與投資建議 (約 150 字):**
626
  - 基於上述所有資訊,提供對該股票的短期和中期市場展望。
627
+ - 提出具體的投資建議,例如:適合何種類型的投資人(價值投資、波段投資、動能投資)、潛在的風險點。
628
  - 請直接提供分析內容,不要包含任何問候語。
629
 
630
  **輸出格式:**
 
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(