Spaces:
Running
Running
Update app.py
Browse files
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,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 |
-
|
| 281 |
-
|
| 282 |
-
'
|
| 283 |
-
'
|
| 284 |
-
'
|
| 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 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 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'],
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 322 |
]
|
| 323 |
|
| 324 |
-
#
|
| 325 |
-
column_names = [
|
| 326 |
-
'close', '
|
| 327 |
-
'
|
|
|
|
| 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(
|
| 340 |
-
print(f"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 341 |
if sentiment_score_raw == 0:
|
| 342 |
print(" ⚠️ 新聞情緒分數為0,可能無新聞數據")
|
| 343 |
else:
|
| 344 |
print(" ✅ 新聞情緒分數正常")
|
| 345 |
|
| 346 |
-
# 技術指標詳細狀態
|
| 347 |
print("\n📈 技術指標狀態:")
|
| 348 |
-
for indicator
|
| 349 |
-
status = tech_indicators_status[indicator]
|
| 350 |
status_symbol = "✅" if status['is_real'] else "⚠️"
|
| 351 |
-
source_info = "
|
| 352 |
-
print(f" {indicator
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 <
|
| 362 |
-
print(" ⚠️ 警告:超過
|
| 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:
|
| 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 =
|
| 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="
|
| 895 |
return fig
|
| 896 |
-
|
| 897 |
df_performance = pd.DataFrame(performance_data)
|
| 898 |
-
|
| 899 |
-
|
| 900 |
-
|
| 901 |
-
|
| 902 |
-
|
| 903 |
-
|
| 904 |
-
|
| 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 |
-
|
| 917 |
-
|
| 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(
|