Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -15,10 +15,19 @@ from plotly.subplots import make_subplots
|
|
| 15 |
import re
|
| 16 |
from bs4 import BeautifulSoup
|
| 17 |
import requests
|
|
|
|
| 18 |
|
| 19 |
# 引用您組員的預測器程式
|
| 20 |
from Bert_predict import BertPredictor
|
| 21 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
# 台股代號對應表 (移除台指期,因為它現在是獨立區塊)
|
| 23 |
TAIWAN_STOCKS = {
|
| 24 |
'元大台灣50': '0050.TW',
|
|
@@ -189,7 +198,6 @@ def get_pmi_data():
|
|
| 189 |
print(f"無法獲取 PMI 資料: {str(e)}")
|
| 190 |
return pd.DataFrame()
|
| 191 |
|
| 192 |
-
# ========================= GEMINI 整合 START =========================
|
| 193 |
def generate_gemini_analysis(stock_name, stock_symbol, period, data):
|
| 194 |
"""
|
| 195 |
使用 Gemini API 生成基本面和市場展望分析。
|
|
@@ -202,7 +210,6 @@ def generate_gemini_analysis(stock_name, stock_symbol, period, data):
|
|
| 202 |
genai.configure(api_key=api_key)
|
| 203 |
model = genai.GenerativeModel('gemini-1.5-flash')
|
| 204 |
|
| 205 |
-
# 準備傳送給模型的數據
|
| 206 |
price_change = ((data['Close'].iloc[-1] - data['Close'].iloc[0]) / data['Close'].iloc[0]) * 100
|
| 207 |
rsi_current = data['RSI'].iloc[-1]
|
| 208 |
macd_current = data['MACD'].iloc[-1]
|
|
@@ -229,7 +236,7 @@ def generate_gemini_analysis(stock_name, stock_symbol, period, data):
|
|
| 229 |
|
| 230 |
2. **市場展望與投資建議 (約 150 字):**
|
| 231 |
- 基於上述所有資訊,提供對該股票的短期和中期市場展望。
|
| 232 |
-
-
|
| 233 |
- 請直接提供分析內容,不要包含任何問候語。
|
| 234 |
|
| 235 |
**輸出格式:**
|
|
@@ -244,13 +251,13 @@ def generate_gemini_analysis(stock_name, stock_symbol, period, data):
|
|
| 244 |
market_outlook = parts[1].strip()
|
| 245 |
return dcc.Markdown(fundamental_analysis), dcc.Markdown(market_outlook)
|
| 246 |
else:
|
| 247 |
-
|
|
|
|
| 248 |
|
| 249 |
except Exception as e:
|
| 250 |
error_message = f"呼叫 Gemini API 時發生錯誤: {str(e)}"
|
| 251 |
print(error_message)
|
| 252 |
-
return error_message, "請檢查後台日誌或 API 金鑰設定"
|
| 253 |
-
# ========================== GEMINI 整合 END ==========================
|
| 254 |
|
| 255 |
# 建立 Dash 應用程式
|
| 256 |
app = dash.Dash(__name__, suppress_callback_exceptions=True)
|
|
@@ -314,7 +321,7 @@ app.layout = html.Div([
|
|
| 314 |
html.Label("時間範圍:"),
|
| 315 |
dcc.Dropdown(id='period-dropdown',
|
| 316 |
options=[{'label': '1個月', 'value': '1mo'},{'label': '3個月', 'value': '3mo'},{'label': '6個月', 'value': '6mo'},{'label': '1年', 'value': '1y'},{'label': '2年', 'value': '2y'}],
|
| 317 |
-
value='1mo', style={'margin-bottom': '10px'})
|
| 318 |
], style={'width': '30%', 'display': 'inline-block', 'margin-left': '5%', 'vertical-align': 'top'}),
|
| 319 |
html.Div([
|
| 320 |
html.Label("圖表類型:"),
|
|
@@ -593,7 +600,7 @@ def update_business_climate_chart(selected_stock):
|
|
| 593 |
fig.update_layout(title="台灣景氣燈號走勢", xaxis_title='日期', yaxis_title='燈號分數', height=300, yaxis=dict(range=[0, 40]))
|
| 594 |
return fig
|
| 595 |
|
| 596 |
-
# ========================= MODIFIED SECTION START =========================
|
| 597 |
@app.callback(
|
| 598 |
[dash.dependencies.Output('technical-analysis-text', 'children'),
|
| 599 |
dash.dependencies.Output('fundamental-analysis-text', 'children'),
|
|
@@ -602,14 +609,29 @@ def update_business_climate_chart(selected_stock):
|
|
| 602 |
dash.dependencies.Input('period-dropdown', 'value')]
|
| 603 |
)
|
| 604 |
def update_analysis_text(selected_stock, period):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 605 |
data = get_stock_data(selected_stock, period)
|
| 606 |
stock_name = [name for name, symbol in TAIWAN_STOCKS.items() if symbol == selected_stock][0]
|
| 607 |
-
if data.empty or len(data) < 20:
|
| 608 |
return "資料不足,無法分析", "資料不足,無法分析", "資料不足,無法分析"
|
| 609 |
|
| 610 |
data = calculate_technical_indicators(data)
|
| 611 |
|
| 612 |
-
#
|
| 613 |
price_change = ((data['Close'].iloc[-1] - data['Close'].iloc[0]) / data['Close'].iloc[0]) * 100
|
| 614 |
rsi_current = data['RSI'].iloc[-1] if not pd.isna(data['RSI'].iloc[-1]) else 50
|
| 615 |
macd_current = data['MACD'].iloc[-1] if not pd.isna(data['MACD'].iloc[-1]) else 0
|
|
@@ -621,17 +643,16 @@ def update_analysis_text(selected_stock, period):
|
|
| 621 |
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}),", f"顯示市場動能偏向{'多頭' if macd_current > macd_signal_current else '空頭'}。"]),
|
| 622 |
])
|
| 623 |
|
| 624 |
-
#
|
| 625 |
-
|
| 626 |
-
loading_text = html.Div([
|
| 627 |
-
dcc.Loading(id="loading-analysis", type="dots", children=[html.Div(id="loading-output")])
|
| 628 |
-
])
|
| 629 |
|
| 630 |
-
|
| 631 |
-
|
| 632 |
-
|
| 633 |
-
fundamental_text
|
| 634 |
-
market_outlook_text
|
|
|
|
|
|
|
| 635 |
|
| 636 |
return technical_text, fundamental_text, market_outlook_text
|
| 637 |
# ========================== MODIFIED SECTION END ==========================
|
|
|
|
| 15 |
import re
|
| 16 |
from bs4 import BeautifulSoup
|
| 17 |
import requests
|
| 18 |
+
import time # 引用 time 模組以處理時間戳
|
| 19 |
|
| 20 |
# 引用您組員的預測器程式
|
| 21 |
from Bert_predict import BertPredictor
|
| 22 |
|
| 23 |
+
# ========================= CACHE 設定 START =========================
|
| 24 |
+
# 分析結果的快取字典
|
| 25 |
+
ANALYSIS_CACHE = {}
|
| 26 |
+
# 快取有效時間(秒),例如:4 小時 = 4 * 60 * 60 = 14400 秒
|
| 27 |
+
CACHE_DURATION_SECONDS = 8 * 60 * 60
|
| 28 |
+
# ========================== CACHE 設定 END ==========================
|
| 29 |
+
|
| 30 |
+
|
| 31 |
# 台股代號對應表 (移除台指期,因為它現在是獨立區塊)
|
| 32 |
TAIWAN_STOCKS = {
|
| 33 |
'元大台灣50': '0050.TW',
|
|
|
|
| 198 |
print(f"無法獲取 PMI 資料: {str(e)}")
|
| 199 |
return pd.DataFrame()
|
| 200 |
|
|
|
|
| 201 |
def generate_gemini_analysis(stock_name, stock_symbol, period, data):
|
| 202 |
"""
|
| 203 |
使用 Gemini API 生成基本面和市場展望分析。
|
|
|
|
| 210 |
genai.configure(api_key=api_key)
|
| 211 |
model = genai.GenerativeModel('gemini-1.5-flash')
|
| 212 |
|
|
|
|
| 213 |
price_change = ((data['Close'].iloc[-1] - data['Close'].iloc[0]) / data['Close'].iloc[0]) * 100
|
| 214 |
rsi_current = data['RSI'].iloc[-1]
|
| 215 |
macd_current = data['MACD'].iloc[-1]
|
|
|
|
| 236 |
|
| 237 |
2. **市場展望與投資建議 (約 150 字):**
|
| 238 |
- 基於上述所有資訊,提供對該股票的短期和中期市場展望。
|
| 239 |
+
- 提出具體的投資建議,例如:適合何種類型的投資人、潛在的風險點、以及建議的觀察價位區間或進出場策略。
|
| 240 |
- 請直接提供分析內容,不要包含任何問候語。
|
| 241 |
|
| 242 |
**輸出格式:**
|
|
|
|
| 251 |
market_outlook = parts[1].strip()
|
| 252 |
return dcc.Markdown(fundamental_analysis), dcc.Markdown(market_outlook)
|
| 253 |
else:
|
| 254 |
+
# Fallback for unexpected response format
|
| 255 |
+
return dcc.Markdown("無法解析 Gemini 回應,請稍後再試。"), dcc.Markdown(response.text)
|
| 256 |
|
| 257 |
except Exception as e:
|
| 258 |
error_message = f"呼叫 Gemini API 時發生錯誤: {str(e)}"
|
| 259 |
print(error_message)
|
| 260 |
+
return dcc.Markdown(error_message), dcc.Markdown("請檢查後台日誌或 API 金鑰設定")
|
|
|
|
| 261 |
|
| 262 |
# 建立 Dash 應用程式
|
| 263 |
app = dash.Dash(__name__, suppress_callback_exceptions=True)
|
|
|
|
| 321 |
html.Label("時間範圍:"),
|
| 322 |
dcc.Dropdown(id='period-dropdown',
|
| 323 |
options=[{'label': '1個月', 'value': '1mo'},{'label': '3個月', 'value': '3mo'},{'label': '6個月', 'value': '6mo'},{'label': '1年', 'value': '1y'},{'label': '2年', 'value': '2y'}],
|
| 324 |
+
value='1mo', style={'margin-bottom': '10px'})
|
| 325 |
], style={'width': '30%', 'display': 'inline-block', 'margin-left': '5%', 'vertical-align': 'top'}),
|
| 326 |
html.Div([
|
| 327 |
html.Label("圖表類型:"),
|
|
|
|
| 600 |
fig.update_layout(title="台灣景氣燈號走勢", xaxis_title='日期', yaxis_title='燈號分數', height=300, yaxis=dict(range=[0, 40]))
|
| 601 |
return fig
|
| 602 |
|
| 603 |
+
# ========================= MODIFIED SECTION START (CACHE INTEGRATED) =========================
|
| 604 |
@app.callback(
|
| 605 |
[dash.dependencies.Output('technical-analysis-text', 'children'),
|
| 606 |
dash.dependencies.Output('fundamental-analysis-text', 'children'),
|
|
|
|
| 609 |
dash.dependencies.Input('period-dropdown', 'value')]
|
| 610 |
)
|
| 611 |
def update_analysis_text(selected_stock, period):
|
| 612 |
+
# 建立快取的唯一鍵值
|
| 613 |
+
cache_key = f"{selected_stock}-{period}"
|
| 614 |
+
current_time = time.time()
|
| 615 |
+
|
| 616 |
+
# 1. 檢查快取
|
| 617 |
+
if cache_key in ANALYSIS_CACHE:
|
| 618 |
+
cached_data = ANALYSIS_CACHE[cache_key]
|
| 619 |
+
if current_time - cached_data['timestamp'] < CACHE_DURATION_SECONDS:
|
| 620 |
+
print(f"從快取載入分析: {cache_key}")
|
| 621 |
+
# 直接回傳快取的內容
|
| 622 |
+
return cached_data['technical'], cached_data['fundamental'], cached_data['outlook']
|
| 623 |
+
|
| 624 |
+
print(f"重新生成分析: {cache_key}")
|
| 625 |
+
# --- 如果快取沒有,才繼續執行以下程式 ---
|
| 626 |
+
|
| 627 |
data = get_stock_data(selected_stock, period)
|
| 628 |
stock_name = [name for name, symbol in TAIWAN_STOCKS.items() if symbol == selected_stock][0]
|
| 629 |
+
if data.empty or len(data) < 20:
|
| 630 |
return "資料不足,無法分析", "資料不足,無法分析", "資料不足,無法分析"
|
| 631 |
|
| 632 |
data = calculate_technical_indicators(data)
|
| 633 |
|
| 634 |
+
# 2. 技術面分析
|
| 635 |
price_change = ((data['Close'].iloc[-1] - data['Close'].iloc[0]) / data['Close'].iloc[0]) * 100
|
| 636 |
rsi_current = data['RSI'].iloc[-1] if not pd.isna(data['RSI'].iloc[-1]) else 50
|
| 637 |
macd_current = data['MACD'].iloc[-1] if not pd.isna(data['MACD'].iloc[-1]) else 0
|
|
|
|
| 643 |
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}),", f"顯示市場動能偏向{'多頭' if macd_current > macd_signal_current else '空頭'}。"]),
|
| 644 |
])
|
| 645 |
|
| 646 |
+
# 3. 基本面與展望分析 (呼叫 Gemini)
|
| 647 |
+
fundamental_text, market_outlook_text = generate_gemini_analysis(stock_name, selected_stock, period, data)
|
|
|
|
|
|
|
|
|
|
| 648 |
|
| 649 |
+
# 4. 將新產生的結果存入快取
|
| 650 |
+
ANALYSIS_CACHE[cache_key] = {
|
| 651 |
+
'technical': technical_text,
|
| 652 |
+
'fundamental': fundamental_text,
|
| 653 |
+
'outlook': market_outlook_text,
|
| 654 |
+
'timestamp': current_time
|
| 655 |
+
}
|
| 656 |
|
| 657 |
return technical_text, fundamental_text, market_outlook_text
|
| 658 |
# ========================== MODIFIED SECTION END ==========================
|