Spaces:
Sleeping
Sleeping
Update app.py
Browse files
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 =
|
| 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 |
-
|
| 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"
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 863 |
-
|
| 864 |
-
# 【重要】使用真實市場因子,而非隨機數
|
| 865 |
-
market_time_adjustment = market_hour_factor + weekday_factor + month_factor
|
| 866 |
|
| 867 |
-
|
| 868 |
-
|
| 869 |
-
|
| 870 |
-
# 預測價格計算
|
| 871 |
-
predicted_price = current_price * trend_factor + base_change + (current_price * market_time_adjustment)
|
| 872 |
|
| 873 |
-
#
|
| 874 |
-
|
| 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 |
-
"""
|
| 946 |
try:
|
| 947 |
-
print(f"
|
| 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 |
-
|
| 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
|
| 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("
|
| 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
|
| 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'] <
|
| 1864 |
-
print(f"使用分析緩存: {cache_key}
|
| 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("
|
| 1892 |
-
html.P([html.Strong("
|
| 1893 |
-
|
| 1894 |
-
|
| 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 |
# 存入緩存
|