AlanRex commited on
Commit
964b3ed
·
verified ·
1 Parent(s): 3e20726

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +74 -167
app.py CHANGED
@@ -533,7 +533,7 @@ ANALYSIS_CACHE = {}
533
  STOCK_DATA_CACHE = {}
534
  CACHE_EXPIRE_SECONDS = 60 # 改為1分鐘,確保數據更及時
535
  # 快取有效時間(秒),例如:8 小時 = 8 * 60 * 60 = 28800 秒
536
- CACHE_DURATION_SECONDS = 8 * 60 * 60
537
  # ========================== CACHE 設定 END ==========================
538
  # ========================== 全域設定 END ==========================
539
 
@@ -740,44 +740,29 @@ def initialize_app():
740
  print(f"分析結果緩存時間: {ANALYSIS_CACHE_DURATION}秒")
741
 
742
  def get_stock_data(symbol, period='1y'):
743
- """改進版獲取股票資料 - 確保及時更新真實數據"""
744
  try:
745
  current_time = time.time()
746
- cache_key = f"{symbol}_{period}"
747
-
748
- # 檢查緩存是否過期
749
- if cache_key in STOCK_DATA_CACHE:
750
- cached_data, cache_time = STOCK_DATA_CACHE[cache_key]
751
- if current_time - cache_time < CACHE_EXPIRE_SECONDS:
752
- print(f"使用緩存數據: {symbol} (緩存時間: {int(current_time - cache_time)}秒前)")
753
- return cached_data
754
- else:
755
- print(f"緩存已過期,重新獲取: {symbol}")
756
 
757
- print(f"正在獲取最新數據: {symbol}")
758
  stock = yf.Ticker(symbol)
759
  data = stock.history(period=period)
760
 
761
- # 備用數據源邏輯
762
  if data.empty and symbol == 'TXF=F':
763
- print("台指期數據為空,嘗試替代數據源...")
764
  stock = yf.Ticker('0050.TW')
765
  data = stock.history(period=period)
766
  if data.empty:
767
  stock = yf.Ticker('^TWII')
768
  data = stock.history(period=period)
769
 
770
- # 儲存到緩存
771
  if not data.empty:
772
- STOCK_DATA_CACHE[cache_key] = (data.copy(), current_time)
773
- print(f"數據獲取成功: {symbol}, 最新日期: {data.index[-1].strftime('%Y-%m-%d')}")
774
- else:
775
- print(f"警告: 無法獲取 {symbol} 的數據")
776
 
777
  return data
778
-
779
  except Exception as e:
780
- print(f"獲取股票數據時發生錯誤: {e}")
781
  return pd.DataFrame()
782
 
783
  def get_us_market_data():
@@ -819,75 +804,42 @@ def get_exchange_rate():
819
  return 31.5
820
 
821
  def simple_statistical_predict(data, predict_days=5):
822
- """改進版統計預測模型 - 基於市場時間的真實變化"""
823
  if len(data) < 60:
824
  return None
825
 
826
  prices = data['Close'].values
827
  current_price = prices[-1]
828
 
829
- # 基本技術指標
830
  ma_short = np.mean(prices[-5:])
831
  ma_medium = np.mean(prices[-20:])
832
  ma_long = np.mean(prices[-60:])
833
  recent_trend = np.polyfit(range(20), prices[-20:], 1)[0]
834
  volatility = np.std(prices[-20:]) / np.mean(prices[-20:])
835
 
836
- # 【關鍵修改】基於當前市場時間的動態因子
837
- now = datetime.now()
838
-
839
- # 市場開盤時間影響因子(台股9:00-13:30)
840
- market_hour_factor = 0
841
- if 9 <= now.hour <= 13:
842
- market_hour_factor = 0.001 * (now.hour - 9) + 0.0001 * now.minute
843
- elif now.hour > 13:
844
- market_hour_factor = -0.001 * (now.hour - 13)
845
-
846
- # 周間效應
847
- weekday_factor = {0: 0.002, 1: 0.001, 2: 0, 3: -0.001, 4: -0.002}.get(now.weekday(), 0)
848
-
849
- # 月份效應(基於歷史統計)
850
- month_factor = {1: 0.001, 2: -0.001, 3: 0.002, 4: 0.001, 5: -0.001, 6: 0,
851
- 7: 0.001, 8: -0.002, 9: -0.001, 10: 0.002, 11: 0.001, 12: 0.003}.get(now.month, 0)
852
 
853
- # 計算預測
854
- base_change = recent_trend * predict_days
855
-
856
- # 趨勢判斷
857
- if ma_short > ma_medium > ma_long:
858
- trend_factor = 1.0 + 0.02 # 多頭趨勢
859
- elif ma_short < ma_medium < ma_long:
860
- trend_factor = 1.0 - 0.02 # 空頭趨勢
861
  else:
862
- trend_factor = 1.0 # 盤整
863
-
864
- # 【重要】使用真實市場因子,而非隨機數
865
- market_time_adjustment = market_hour_factor + weekday_factor + month_factor
866
 
867
- # 波動性調整(基於實際歷史波動)
868
- volatility_adjustment = volatility * predict_days * 0.5
869
-
870
- # 預測價格計算
871
- predicted_price = current_price * trend_factor + base_change + (current_price * market_time_adjustment)
872
 
873
- # 考慮預測天數的不確定性遞增
874
- uncertainty_factor = 1 + (predict_days / 100.0) * volatility
875
- predicted_price *= uncertainty_factor
876
 
877
  # 計算漲幅百分比
878
  change_pct = ((predicted_price - current_price) / current_price) * 100
 
879
 
880
- # 合理範圍限制
881
- change_pct = np.clip(change_pct, -20.0, 20.0)
882
-
883
- # 信心度基於數據質量和市場狀況
884
- confidence = max(0.5, min(0.9, (1 - volatility * 3) + abs(market_time_adjustment) * 10))
885
 
886
- print(f"統計預測詳情:")
887
- print(f" - 市場時間因子: {market_time_adjustment:.6f}")
888
- print(f" - 趨勢因子: {trend_factor:.3f}")
889
- print(f" - 波動率: {volatility:.4f}")
890
- print(f" - 預測漲幅: {change_pct:+.2f}%")
891
 
892
  return {
893
  'predicted_price': predicted_price,
@@ -942,76 +894,43 @@ def calculate_new_features(df):
942
  return df
943
 
944
  def advanced_xgboost_predict(predict_days=5):
945
- """改進版 XGBoost 預測 - 增加真實的市場時間特徵"""
946
  try:
947
- print(f"開始使用 XGBoost 模型進行 {predict_days} 天預測...")
948
 
949
  xgb_model = XGBoostModel()
950
- current_time = datetime.now()
951
 
952
- # 獲取最新的台指期數據
 
953
  taiex_data = get_stock_data('^TWII', '2y')
954
  if taiex_data.empty or len(taiex_data) < 60:
955
  print("台指期數據不足,無法進行XGBoost預測")
956
  return None
957
 
958
- # 計算技術指標
959
  taiex_data = calculate_technical_indicators(taiex_data)
960
  taiex_data = calculate_new_features(taiex_data)
961
 
962
- # 【關鍵修改】獲取真實的市場數據,而非使用緩存
963
  # 強制重新獲取美股數據
964
- print("正在獲取最新美股數據...")
965
-
966
- # 清除美股數據的緩存,確保獲取最新數據
967
- us_symbols = ['^DJI', '^SOX']
968
- for symbol in us_symbols:
969
- cache_key = f"{symbol}_5d"
970
- if cache_key in STOCK_DATA_CACHE:
971
- del STOCK_DATA_CACHE[cache_key]
972
-
973
- # 獲取美股數據
974
- dji_return = 0
975
- sox_return = 0
976
 
977
- try:
978
- dji_data = get_stock_data('^DJI', '5d')
979
- if not dji_data.empty and len(dji_data) >= 2:
980
- dji_return = (dji_data['Close'].iloc[-1] / dji_data['Close'].iloc[-2] - 1)
981
- print(f"道瓊報酬率: {dji_return:.4f}")
982
- except Exception as e:
983
- print(f"無法獲取道瓊數據: {e}")
984
- dji_return = 0
985
-
986
- try:
987
- sox_data = get_stock_data('^SOX', '5d')
988
- if not sox_data.empty and len(sox_data) >= 2:
989
- sox_return = (sox_data['Close'].iloc[-1] / sox_data['Close'].iloc[-2] - 1)
990
- print(f"費半報酬率: {sox_return:.4f}")
991
- except Exception as e:
992
- print(f"無法獲取費半數據: {e}")
993
- sox_return = 0
994
-
995
- # 獲取最新新聞情緒分數
996
- sentiment_score_raw = 0
997
  try:
998
  if predictor is not None:
999
- # 清除新聞緩存,確保獲取最新情緒
1000
  sentiment_score_raw = predictor.get_news_index()
1001
- print(f"新聞情緒分數: {sentiment_score_raw:.4f}")
1002
  if sentiment_score_raw is None:
1003
  sentiment_score_raw = 0
 
1004
  else:
1005
  sentiment_score_raw = 0
1006
- except Exception as e:
1007
- print(f"無法獲取新聞情緒: {e}")
1008
  sentiment_score_raw = 0
1009
 
1010
  # 準備特徵數據
1011
  latest_data = taiex_data.iloc[-1]
1012
  yesterday_close = latest_data['Close']
1013
-
1014
- # 構建特徵向量
1015
  new_feature_columns = [
1016
  'return_t-1',
1017
  'return_t-5',
@@ -1021,15 +940,34 @@ def advanced_xgboost_predict(predict_days=5):
1021
  'MACD_diff',
1022
  ]
1023
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1024
  features_list = []
1025
  feature_names = []
1026
 
1027
- # 處理技術指標特徵
1028
  for feature in new_feature_columns:
1029
  if feature in latest_data.index:
1030
  value = latest_data[feature]
1031
  if pd.isna(value):
1032
- # 使用合理的預設值
1033
  if 'return' in feature: default_value = 0.0
1034
  elif 'MA' in feature: default_value = latest_data['Close'] if not pd.isna(latest_data['Close']) else 100
1035
  elif 'volatility' in feature: default_value = 0.02
@@ -1043,28 +981,19 @@ def advanced_xgboost_predict(predict_days=5):
1043
 
1044
  feature_names.append(feature)
1045
 
1046
- # 添加美股數據
1047
- features_list.extend([dji_return, sox_return])
1048
- feature_names.extend(['dji_return_t-1', 'sox_return_t-1'])
1049
-
1050
- # 添加收盤價和新聞數據
1051
- features_list.extend([yesterday_close, sentiment_score_raw])
1052
- feature_names.extend(['close', 'NEWS'])
1053
 
1054
  # 轉換為 DataFrame
1055
  input_df = pd.DataFrame([features_list], columns=feature_names)
1056
 
1057
- print("=" * 60)
1058
- print(f"XGBoost 模型輸入特徵 (時間: {current_time.strftime('%Y-%m-%d %H:%M:%S')})")
1059
- print("=" * 60)
1060
- print(f"特徵向量: {[f'{v:.6f}' for v in features_list]}")
1061
- print("=" * 60)
1062
 
1063
  # 進行預測
1064
  predictions = xgb_model.predict('xgboost_model', input_df)
1065
 
1066
  if predictions is None:
1067
- print("XGBoost 預測失敗")
1068
  return None
1069
 
1070
  # 處理預測結果
@@ -1083,21 +1012,16 @@ def advanced_xgboost_predict(predict_days=5):
1083
  current_price = latest_data['Close']
1084
  predicted_price = current_price * (1 + predicted_change_pct / 100)
1085
 
1086
- print(f"XGBoost 預測結果:")
1087
- print(f"- 預測天數: {predict_days} (使用 {closest_day} 天模型)")
1088
- print(f"- 當前價格: {current_price:.2f}")
1089
- print(f"- 預測漲幅: {predicted_change_pct:+.2f}%")
1090
- print(f"- 預測價格: {predicted_price:.2f}")
1091
- print(f"- 數據時間: {taiex_data.index[-1].strftime('%Y-%m-%d')}")
1092
 
1093
  return {
1094
  'predicted_price': predicted_price,
1095
  'change_pct': predicted_change_pct,
1096
- 'confidence': 0.75 # 基於模型質量的固定信心度
1097
  }
1098
 
1099
  except Exception as e:
1100
- print(f"XGBoost 預測時發生錯誤: {e}")
1101
  import traceback
1102
  traceback.print_exc()
1103
  return None
@@ -1849,63 +1773,46 @@ def update_business_climate_chart(selected_stock):
1849
  [Input('stock-dropdown', 'value'),
1850
  Input('period-dropdown', 'value')]
1851
  )
1852
- ANALYSIS_CACHE = {}
1853
- ANALYSIS_CACHE_DURATION = 60 # 改為1分鐘
1854
-
1855
  def update_analysis_text(selected_stock, period):
1856
- """修改版分析文本更新 - 縮短緩存時間"""
 
 
1857
  cache_key = f"{selected_stock}-{period}"
1858
  current_time = time.time()
1859
 
1860
  # 檢查緩存
1861
  if cache_key in ANALYSIS_CACHE:
1862
  cached_data = ANALYSIS_CACHE[cache_key]
1863
- if current_time - cached_data['timestamp'] < ANALYSIS_CACHE_DURATION:
1864
- print(f"使用分析緩存: {cache_key} (剩餘: {int(ANALYSIS_CACHE_DURATION - (current_time - cached_data['timestamp']))}秒)")
1865
  return cached_data['technical'], cached_data['fundamental'], cached_data['outlook']
1866
 
1867
- print(f"重新生成分析: {cache_key}")
1868
-
1869
- # 清理舊緩存
1870
- clear_old_cache()
1871
 
1872
- # 獲取最新數據
1873
  data = get_stock_data(selected_stock, period)
1874
  stock_name = [name for name, symbol in TAIWAN_STOCKS.items() if symbol == selected_stock][0]
1875
-
1876
  if data.empty or len(data) < 20:
1877
  return "資料不足,無法分析", "資料不足,無法分析", "資料不足,無法分析"
1878
 
1879
  data = calculate_technical_indicators(data)
1880
 
1881
- # 技術面分析(使用最新數據)
1882
  price_change = ((data['Close'].iloc[-1] - data['Close'].iloc[0]) / data['Close'].iloc[0]) * 100
1883
  rsi_current = data['RSI'].iloc[-1] if not pd.isna(data['RSI'].iloc[-1]) else 50
1884
  macd_current = data['MACD'].iloc[-1] if not pd.isna(data['MACD'].iloc[-1]) else 0
1885
  macd_signal_current = data['MACD_Signal'].iloc[-1] if not pd.isna(data['MACD_Signal'].iloc[-1]) else 0
1886
 
1887
- # 添加數據時間戳信息
1888
  latest_date = data.index[-1].strftime('%Y-%m-%d')
 
1889
 
1890
  technical_text = html.Div([
1891
- html.P([html.Strong("數據時間:"), f"{latest_date}"]),
1892
- html.P([html.Strong("價格趋勢:"), f"在最近 {period} 期間內,{stock_name} 股價呈現",
1893
- html.Span(f"{'上漲' if price_change > 5 else '下跌' if price_change < -5 else '盤整'}",
1894
- style={'color': 'red' if price_change > 5 else 'green' if price_change < -5 else 'orange',
1895
- 'font-weight': 'bold'}),
1896
- f"走勢,累計變動 {price_change:+.1f}%。"]),
1897
- html.P([html.Strong("RSI 指標:"), f"目前的 RSI 值為 {rsi_current:.1f},",
1898
- html.Span("處於超買區(>70)" if rsi_current > 70 else "處於超賣區(<30)" if rsi_current < 30 else "在正常範圍內",
1899
- style={'color': 'green' if rsi_current > 70 else 'red' if rsi_current < 30 else 'blue',
1900
- 'font-weight': 'bold'}), "。"]),
1901
- html.P([html.Strong("MACD 指標:"), f"MACD 快線 ({macd_current:.3f}) 目前",
1902
- html.Span("高於" if macd_current > macd_signal_current else "低於",
1903
- style={'color': 'red' if macd_current > macd_signal_current else 'green',
1904
- 'font-weight': 'bold'}),
1905
- f" Signal 慢線 ({macd_signal_current:.3f}),顯示市場動能偏向{'多頭' if macd_current > macd_signal_current else '空頭'}。"]),
1906
  ])
1907
 
1908
- # AI 分析(基於最新數據)
1909
  fundamental_text, market_outlook_text = generate_gemini_analysis(stock_name, selected_stock, period, data)
1910
 
1911
  # 存入緩存
 
533
  STOCK_DATA_CACHE = {}
534
  CACHE_EXPIRE_SECONDS = 60 # 改為1分鐘,確保數據更及時
535
  # 快取有效時間(秒),例如:8 小時 = 8 * 60 * 60 = 28800 秒
536
+ CACHE_DURATION_SECONDS = 60 # 60秒緩存
537
  # ========================== CACHE 設定 END ==========================
538
  # ========================== 全域設定 END ==========================
539
 
 
740
  print(f"分析結果緩存時間: {ANALYSIS_CACHE_DURATION}秒")
741
 
742
  def get_stock_data(symbol, period='1y'):
743
+ """獲取股票資料 - 改進版,縮短緩存時間"""
744
  try:
745
  current_time = time.time()
746
+ cache_key = f"{symbol}_{period}_{int(current_time/60)}" # 每分鐘一個新的cache key
747
+
748
+ print(f"正在獲取股票數據: {symbol}, 時間: {datetime.now().strftime('%H:%M:%S')}")
 
 
 
 
 
 
 
749
 
 
750
  stock = yf.Ticker(symbol)
751
  data = stock.history(period=period)
752
 
 
753
  if data.empty and symbol == 'TXF=F':
 
754
  stock = yf.Ticker('0050.TW')
755
  data = stock.history(period=period)
756
  if data.empty:
757
  stock = yf.Ticker('^TWII')
758
  data = stock.history(period=period)
759
 
 
760
  if not data.empty:
761
+ print(f"數據獲取成功: {symbol}, 最新日期: {data.index[-1].strftime('%Y-%m-%d')}, 最新收盤: {data['Close'].iloc[-1]:.2f}")
 
 
 
762
 
763
  return data
 
764
  except Exception as e:
765
+ print(f"獲取股票數據錯誤: {e}")
766
  return pd.DataFrame()
767
 
768
  def get_us_market_data():
 
804
  return 31.5
805
 
806
  def simple_statistical_predict(data, predict_days=5):
807
+ """簡化的統計預測模型 - 加入時間變化因子"""
808
  if len(data) < 60:
809
  return None
810
 
811
  prices = data['Close'].values
812
  current_price = prices[-1]
813
 
 
814
  ma_short = np.mean(prices[-5:])
815
  ma_medium = np.mean(prices[-20:])
816
  ma_long = np.mean(prices[-60:])
817
  recent_trend = np.polyfit(range(20), prices[-20:], 1)[0]
818
  volatility = np.std(prices[-20:]) / np.mean(prices[-20:])
819
 
820
+ # 使用當前時間作為變化因子
821
+ current_hour = datetime.now().hour
822
+ current_minute = datetime.now().minute
 
 
 
 
 
 
 
 
 
 
 
 
 
823
 
824
+ # 市場時間調整因子(台股9:00-13:30)
825
+ if 9 <= current_hour <= 13:
826
+ time_factor = (current_hour - 9) * 0.001 + current_minute * 0.00001
 
 
 
 
 
827
  else:
828
+ time_factor = current_minute * 0.00001
 
 
 
829
 
830
+ base_change = recent_trend * predict_days
831
+ trend_factor = 1.0 + (0.02 if ma_short > ma_medium > ma_long else -0.02 if ma_short < ma_medium < ma_long else 0)
 
 
 
832
 
833
+ # 加入時間變化
834
+ predicted_price = current_price * trend_factor + base_change + (current_price * time_factor)
 
835
 
836
  # 計算漲幅百分比
837
  change_pct = ((predicted_price - current_price) / current_price) * 100
838
+ change_pct = np.clip(change_pct, -15.0, 15.0)
839
 
840
+ confidence = max(0.6, 1 - volatility * 2)
 
 
 
 
841
 
842
+ print(f"統計預測 - 時間因子: {time_factor:.6f}, 漲幅: {change_pct:+.2f}%")
 
 
 
 
843
 
844
  return {
845
  'predicted_price': predicted_price,
 
894
  return df
895
 
896
  def advanced_xgboost_predict(predict_days=5):
897
+ """使用 XGBoost 模型進行預測 - 強制刷新數據版本"""
898
  try:
899
+ print(f"開始XGBoost預測 - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
900
 
901
  xgb_model = XGBoostModel()
 
902
 
903
+ # 強制重新獲取台指數據 - 不使用緩存
904
+ print("正在獲取最新台指數據...")
905
  taiex_data = get_stock_data('^TWII', '2y')
906
  if taiex_data.empty or len(taiex_data) < 60:
907
  print("台指期數據不足,無法進行XGBoost預測")
908
  return None
909
 
 
910
  taiex_data = calculate_technical_indicators(taiex_data)
911
  taiex_data = calculate_new_features(taiex_data)
912
 
 
913
  # 強制重新獲取美股數據
914
+ print("正在獲取美股數據...")
915
+ us_market_data = get_us_market_data()
 
 
 
 
 
 
 
 
 
 
916
 
917
+ # 獲取新聞情緒分數
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
918
  try:
919
  if predictor is not None:
 
920
  sentiment_score_raw = predictor.get_news_index()
 
921
  if sentiment_score_raw is None:
922
  sentiment_score_raw = 0
923
+ print(f"新聞情緒分數: {sentiment_score_raw}")
924
  else:
925
  sentiment_score_raw = 0
926
+ except:
 
927
  sentiment_score_raw = 0
928
 
929
  # 準備特徵數據
930
  latest_data = taiex_data.iloc[-1]
931
  yesterday_close = latest_data['Close']
932
+
933
+ # 特徵列表保持不變
934
  new_feature_columns = [
935
  'return_t-1',
936
  'return_t-5',
 
940
  'MACD_diff',
941
  ]
942
 
943
+ # 獲取美股報酬率
944
+ dji_return = 0
945
+ sox_return = 0
946
+
947
+ try:
948
+ dji_data = get_stock_data('^DJI', '5d')
949
+ if not dji_data.empty and len(dji_data) >= 2:
950
+ dji_return = (dji_data['Close'].iloc[-1] / dji_data['Close'].iloc[-2] - 1)
951
+ print(f"道瓊報酬率: {dji_return:.4f}")
952
+ except:
953
+ pass
954
+
955
+ try:
956
+ sox_data = get_stock_data('^SOX', '5d')
957
+ if not sox_data.empty and len(sox_data) >= 2:
958
+ sox_return = (sox_data['Close'].iloc[-1] / sox_data['Close'].iloc[-2] - 1)
959
+ print(f"費半報酬率: {sox_return:.4f}")
960
+ except:
961
+ pass
962
+
963
+ # 建立特徵向量
964
  features_list = []
965
  feature_names = []
966
 
 
967
  for feature in new_feature_columns:
968
  if feature in latest_data.index:
969
  value = latest_data[feature]
970
  if pd.isna(value):
 
971
  if 'return' in feature: default_value = 0.0
972
  elif 'MA' in feature: default_value = latest_data['Close'] if not pd.isna(latest_data['Close']) else 100
973
  elif 'volatility' in feature: default_value = 0.02
 
981
 
982
  feature_names.append(feature)
983
 
984
+ # 添加其他特徵
985
+ features_list.extend([dji_return, sox_return, yesterday_close, sentiment_score_raw])
986
+ feature_names.extend(['dji_return_t-1', 'sox_return_t-1', 'close', 'NEWS'])
 
 
 
 
987
 
988
  # 轉換為 DataFrame
989
  input_df = pd.DataFrame([features_list], columns=feature_names)
990
 
991
+ print(f"特徵向量: {[f'{f:.4f}' for f in features_list[:5]]}...") # 只顯示前5個
 
 
 
 
992
 
993
  # 進行預測
994
  predictions = xgb_model.predict('xgboost_model', input_df)
995
 
996
  if predictions is None:
 
997
  return None
998
 
999
  # 處理預測結果
 
1012
  current_price = latest_data['Close']
1013
  predicted_price = current_price * (1 + predicted_change_pct / 100)
1014
 
1015
+ print(f"XGBoost預測結果 - 漲幅: {predicted_change_pct:+.2f}%, 時間: {datetime.now().strftime('%H:%M:%S')}")
 
 
 
 
 
1016
 
1017
  return {
1018
  'predicted_price': predicted_price,
1019
  'change_pct': predicted_change_pct,
1020
+ 'confidence': 0.75
1021
  }
1022
 
1023
  except Exception as e:
1024
+ print(f"XGBoost 預測錯誤: {e}")
1025
  import traceback
1026
  traceback.print_exc()
1027
  return None
 
1773
  [Input('stock-dropdown', 'value'),
1774
  Input('period-dropdown', 'value')]
1775
  )
 
 
 
1776
  def update_analysis_text(selected_stock, period):
1777
+ """修改版分析文本更新"""
1778
+ # 縮短緩存時間為1分鐘
1779
+ cache_duration = 60
1780
  cache_key = f"{selected_stock}-{period}"
1781
  current_time = time.time()
1782
 
1783
  # 檢查緩存
1784
  if cache_key in ANALYSIS_CACHE:
1785
  cached_data = ANALYSIS_CACHE[cache_key]
1786
+ if current_time - cached_data['timestamp'] < cache_duration:
1787
+ print(f"使用分析緩存: {cache_key}")
1788
  return cached_data['technical'], cached_data['fundamental'], cached_data['outlook']
1789
 
1790
+ print(f"重新生成分析: {cache_key} - {datetime.now().strftime('%H:%M:%S')}")
 
 
 
1791
 
 
1792
  data = get_stock_data(selected_stock, period)
1793
  stock_name = [name for name, symbol in TAIWAN_STOCKS.items() if symbol == selected_stock][0]
 
1794
  if data.empty or len(data) < 20:
1795
  return "資料不足,無法分析", "資料不足,無法分析", "資料不足,無法分析"
1796
 
1797
  data = calculate_technical_indicators(data)
1798
 
1799
+ # 技術面分析
1800
  price_change = ((data['Close'].iloc[-1] - data['Close'].iloc[0]) / data['Close'].iloc[0]) * 100
1801
  rsi_current = data['RSI'].iloc[-1] if not pd.isna(data['RSI'].iloc[-1]) else 50
1802
  macd_current = data['MACD'].iloc[-1] if not pd.isna(data['MACD'].iloc[-1]) else 0
1803
  macd_signal_current = data['MACD_Signal'].iloc[-1] if not pd.isna(data['MACD_Signal'].iloc[-1]) else 0
1804
 
 
1805
  latest_date = data.index[-1].strftime('%Y-%m-%d')
1806
+ current_time_str = datetime.now().strftime('%H:%M:%S')
1807
 
1808
  technical_text = html.Div([
1809
+ html.P([html.Strong("更新時間:"), f"{latest_date} {current_time_str}"]),
1810
+ html.P([html.Strong("價格趨勢:"), f"在最近 {period} 期間內,{stock_name} 股價呈現", html.Span(f"{'上漲' if price_change > 5 else '下跌' if price_change < -5 else '盤整'}", style={'color': 'red' if price_change > 5 else 'green' if price_change < -5 else 'orange', 'font-weight': 'bold'}), f"走勢,累計變動 {price_change:+.1f}%。"]),
1811
+ html.P([html.Strong("RSI 指標:"), f"目前的 RSI 值為 {rsi_current:.1f},", html.Span("處於超買區(>70)" if rsi_current > 70 else "處於超賣區(<30)" if rsi_current < 30 else "在正常範圍內", style={'color': 'green' if rsi_current > 70 else 'red' if rsi_current < 30 else 'blue', 'font-weight': 'bold'}), "。"]),
1812
+ 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})。"]),
 
 
 
 
 
 
 
 
 
 
 
1813
  ])
1814
 
1815
+ # AI 分析
1816
  fundamental_text, market_outlook_text = generate_gemini_analysis(stock_name, selected_stock, period, data)
1817
 
1818
  # 存入緩存