Spaces:
Running
Running
o4
Browse files
app.py
CHANGED
@@ -17,7 +17,7 @@ import pandas as pd
|
|
17 |
import dotenv
|
18 |
dotenv.load_dotenv()
|
19 |
|
20 |
-
# --- 新版 Prompt ---
|
21 |
FUNDAMENTAL_ANALYST_PROMPT = """
|
22 |
You are a fundamental analyst specializing in evaluating company (whose symbol is {company}) performance based on stock prices, technical indicators, financial metrics, recent news, industry trends, competitor positioning, and financial ratios. Your task is to provide a comprehensive summary.
|
23 |
|
@@ -47,35 +47,43 @@ You have access to the following tools:
|
|
47 |
|
48 |
---
|
49 |
|
50 |
-
### Output Format
|
51 |
-
|
52 |
-
"stock": "<Stock Symbol>",
|
53 |
-
"price_analysis": "<股票價格趨勢與技術指標分析>",
|
54 |
-
"technical_analysis": "<技術指標分析與見解>",
|
55 |
-
"financial_analysis": {
|
56 |
-
"profitability_ratios": "<獲利能力比率分析>",
|
57 |
-
"liquidity_ratios": "<流動性比率分析>",
|
58 |
-
"solvency_ratios": "<償債能力比率分析>",
|
59 |
-
"efficiency_ratios": "<營運效率比率分析>",
|
60 |
-
"market_ratios": "<市場表現比率分析>",
|
61 |
-
"summary": "<財務整體健康狀況與分析結論>"
|
62 |
-
},
|
63 |
-
"news_analysis": "<近期新聞摘要與其對股價的潛在影響>",
|
64 |
-
"industry_analysis": "<產業趨勢、成長動力與潛在風險>",
|
65 |
-
"competitor_analysis": "<主要競爭對手比較與市場地位分析>",
|
66 |
-
"final_summary": "<整體綜合結論與投資建議>",
|
67 |
-
"Asked Question Answer": "<根據上述分析的具體回答>"
|
68 |
-
}
|
69 |
|
70 |
-
|
|
|
71 |
|
72 |
-
|
73 |
-
-
|
74 |
-
|
75 |
-
|
76 |
-
-
|
77 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
78 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
79 |
|
80 |
def period_to_start_end(period_str: str):
|
81 |
"""
|
@@ -93,7 +101,6 @@ def period_to_start_end(period_str: str):
|
|
93 |
start = now - dt.timedelta(weeks=13)
|
94 |
return start, now
|
95 |
|
96 |
-
|
97 |
def get_stock_prices(ticker: str, period: str = "3mo") -> Union[Dict, str]:
|
98 |
"""Fetches historical stock price data and technical indicators for a given ticker."""
|
99 |
try:
|
@@ -184,12 +191,10 @@ def get_stock_prices(ticker: str, period: str = "3mo") -> Union[Dict, str]:
|
|
184 |
"stock_price": df_for_output.to_dict(orient="records"),
|
185 |
"indicators": indicators
|
186 |
}
|
187 |
-
|
188 |
except Exception as e:
|
189 |
print(f"[get_stock_prices] Exception: {str(e)}")
|
190 |
return f"Error fetching price data: {str(e)}"
|
191 |
|
192 |
-
|
193 |
def get_financial_news(ticker: str) -> Union[Dict, str]:
|
194 |
"""Fetches the latest financial news related to a given ticker."""
|
195 |
try:
|
@@ -201,23 +206,23 @@ def get_financial_news(ticker: str) -> Union[Dict, str]:
|
|
201 |
print("[get_financial_news] No news data returned from yfinance.")
|
202 |
return {"news": "No recent news found."}
|
203 |
|
204 |
-
#
|
205 |
latest_news = []
|
206 |
-
for item in news[:5]:
|
|
|
207 |
latest_news.append({
|
208 |
-
"title": item.get(
|
209 |
-
"publisher": item.get(
|
210 |
-
"link": item.get(
|
211 |
-
"published_date": item.get(
|
212 |
})
|
213 |
|
214 |
-
print(f"[get_financial_news] Found {len(latest_news)} news items.")
|
215 |
return {"news": latest_news}
|
216 |
except Exception as e:
|
217 |
print(f"[get_financial_news] Exception: {str(e)}")
|
218 |
return f"Error fetching news: {str(e)}"
|
219 |
|
220 |
-
|
221 |
def get_financial_metrics(ticker: str) -> Union[Dict, str]:
|
222 |
"""Fetches key financial ratios for a given ticker."""
|
223 |
try:
|
@@ -225,7 +230,6 @@ def get_financial_metrics(ticker: str) -> Union[Dict, str]:
|
|
225 |
stock = yf.Ticker(ticker)
|
226 |
info = stock.info
|
227 |
|
228 |
-
# 顯示完整 info 以便除錯
|
229 |
print(f"[get_financial_metrics] Info returned:\n{json.dumps(info, indent=2)}")
|
230 |
|
231 |
return {
|
@@ -238,11 +242,10 @@ def get_financial_metrics(ticker: str) -> Union[Dict, str]:
|
|
238 |
print(f"[get_financial_metrics] Exception: {str(e)}")
|
239 |
return f"Error fetching ratios: {str(e)}"
|
240 |
|
241 |
-
|
242 |
def analyze_stock(api_key: str, ticker: str, period: str) -> str:
|
243 |
"""
|
244 |
根據輸入的 LLM API key、股票代號與時間區間,抓取各項資料後,
|
245 |
-
組合成分析 Prompt 呼叫 LLM
|
246 |
"""
|
247 |
try:
|
248 |
# 建立 LLM 實例
|
@@ -278,13 +281,11 @@ def analyze_stock(api_key: str, ticker: str, period: str) -> str:
|
|
278 |
print("[analyze_stock] Sending prompt to LLM...")
|
279 |
response = llm.invoke(analysis_prompt)
|
280 |
|
281 |
-
return response.content
|
282 |
-
|
283 |
except Exception as e:
|
284 |
print(f"[analyze_stock] Exception: {str(e)}")
|
285 |
return f"分析過程中發生錯誤: {str(e)}\n{traceback.format_exc()}"
|
286 |
|
287 |
-
|
288 |
# --- Gradio 介面 ---
|
289 |
iface = gr.Interface(
|
290 |
fn=analyze_stock,
|
@@ -295,7 +296,7 @@ iface = gr.Interface(
|
|
295 |
],
|
296 |
outputs=gr.Textbox(label="基本面分析結果"),
|
297 |
title="股票基本面分析 App",
|
298 |
-
description="輸入您的 LLM API key
|
299 |
)
|
300 |
|
301 |
if __name__ == "__main__":
|
|
|
17 |
import dotenv
|
18 |
dotenv.load_dotenv()
|
19 |
|
20 |
+
# --- 新版 Prompt(改為人類可讀的非 JSON 輸出) ---
|
21 |
FUNDAMENTAL_ANALYST_PROMPT = """
|
22 |
You are a fundamental analyst specializing in evaluating company (whose symbol is {company}) performance based on stock prices, technical indicators, financial metrics, recent news, industry trends, competitor positioning, and financial ratios. Your task is to provide a comprehensive summary.
|
23 |
|
|
|
47 |
|
48 |
---
|
49 |
|
50 |
+
### Output Format: 以下請用繁體中文輸出,且以「易讀文字段落/清單」方式呈現 (非 JSON):
|
51 |
+
請根據以下結構,撰寫一份詳細的分析報告:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
52 |
|
53 |
+
1. 【股票代碼】
|
54 |
+
- 說明:顯示本次分析所針對的股票代號。
|
55 |
|
56 |
+
2. 【股票價格趨勢與技術指標分析】
|
57 |
+
- 說明:闡述近期股價變化趨勢、波動情況,並搭配 RSI、MACD、VWAP 等技術指標,提供觀察與解讀。
|
58 |
+
|
59 |
+
3. 【技術指標分析與見解】
|
60 |
+
- 說明:進一步解釋 RSI、MACD 等所呈現的訊號,與可能的後續走勢。
|
61 |
+
|
62 |
+
4. 【財務分析】
|
63 |
+
- 4.1 獲利能力比率分析
|
64 |
+
- 4.2 流動性比率分析
|
65 |
+
- 4.3 償債能力比率分析
|
66 |
+
- 4.4 營運效率比率分析
|
67 |
+
- 4.5 市場表現比率分析
|
68 |
+
- 4.6 總結財務健康狀況
|
69 |
+
|
70 |
+
5. 【新聞分析】
|
71 |
+
- 說明:列出近期相關新聞及其對股價、企業形象可能產生的影響。
|
72 |
|
73 |
+
6. 【產業分析】
|
74 |
+
- 說明:討論產業趨勢、發展動能與可能風險。
|
75 |
+
|
76 |
+
7. 【競爭對手分析】
|
77 |
+
- 說明:比較主要競爭對手之優勢與劣勢,以及對目標公司的影響。
|
78 |
+
|
79 |
+
8. 【最終結論與投資建議】
|
80 |
+
- 說明:歸納整體分析結果,給出投資人應採取之行動或建議。
|
81 |
+
|
82 |
+
9. 【回答使用者問題】
|
83 |
+
- 針對「Should I buy this stock?」之回應,提出依據上述分析的結論性建議。
|
84 |
+
|
85 |
+
※ 請確保敘述完整、清晰、條理分明、方便一般讀者閱讀。同時避免使用 JSON 格式,改以段落或清單式敘述。
|
86 |
+
"""
|
87 |
|
88 |
def period_to_start_end(period_str: str):
|
89 |
"""
|
|
|
101 |
start = now - dt.timedelta(weeks=13)
|
102 |
return start, now
|
103 |
|
|
|
104 |
def get_stock_prices(ticker: str, period: str = "3mo") -> Union[Dict, str]:
|
105 |
"""Fetches historical stock price data and technical indicators for a given ticker."""
|
106 |
try:
|
|
|
191 |
"stock_price": df_for_output.to_dict(orient="records"),
|
192 |
"indicators": indicators
|
193 |
}
|
|
|
194 |
except Exception as e:
|
195 |
print(f"[get_stock_prices] Exception: {str(e)}")
|
196 |
return f"Error fetching price data: {str(e)}"
|
197 |
|
|
|
198 |
def get_financial_news(ticker: str) -> Union[Dict, str]:
|
199 |
"""Fetches the latest financial news related to a given ticker."""
|
200 |
try:
|
|
|
206 |
print("[get_financial_news] No news data returned from yfinance.")
|
207 |
return {"news": "No recent news found."}
|
208 |
|
209 |
+
# 詳細列印前 5 則新聞資訊
|
210 |
latest_news = []
|
211 |
+
for i, item in enumerate(news[:5], start=1):
|
212 |
+
print(f"[get_financial_news] News item {i} RAW: {json.dumps(item, indent=2)}")
|
213 |
latest_news.append({
|
214 |
+
"title": item.get("title"),
|
215 |
+
"publisher": item.get("publisher"),
|
216 |
+
"link": item.get("link"),
|
217 |
+
"published_date": item.get("providerPublishTime")
|
218 |
})
|
219 |
|
220 |
+
print(f"[get_financial_news] Found {len(latest_news)} news items (detailed above).")
|
221 |
return {"news": latest_news}
|
222 |
except Exception as e:
|
223 |
print(f"[get_financial_news] Exception: {str(e)}")
|
224 |
return f"Error fetching news: {str(e)}"
|
225 |
|
|
|
226 |
def get_financial_metrics(ticker: str) -> Union[Dict, str]:
|
227 |
"""Fetches key financial ratios for a given ticker."""
|
228 |
try:
|
|
|
230 |
stock = yf.Ticker(ticker)
|
231 |
info = stock.info
|
232 |
|
|
|
233 |
print(f"[get_financial_metrics] Info returned:\n{json.dumps(info, indent=2)}")
|
234 |
|
235 |
return {
|
|
|
242 |
print(f"[get_financial_metrics] Exception: {str(e)}")
|
243 |
return f"Error fetching ratios: {str(e)}"
|
244 |
|
|
|
245 |
def analyze_stock(api_key: str, ticker: str, period: str) -> str:
|
246 |
"""
|
247 |
根據輸入的 LLM API key、股票代號與時間區間,抓取各項資料後,
|
248 |
+
組合成分析 Prompt 呼叫 LLM,最後回傳『易讀文字段落』的基本面分析結果。
|
249 |
"""
|
250 |
try:
|
251 |
# 建立 LLM 實例
|
|
|
281 |
print("[analyze_stock] Sending prompt to LLM...")
|
282 |
response = llm.invoke(analysis_prompt)
|
283 |
|
284 |
+
return response.content # 回傳「非 JSON」的易讀文字段落
|
|
|
285 |
except Exception as e:
|
286 |
print(f"[analyze_stock] Exception: {str(e)}")
|
287 |
return f"分析過程中發生錯誤: {str(e)}\n{traceback.format_exc()}"
|
288 |
|
|
|
289 |
# --- Gradio 介面 ---
|
290 |
iface = gr.Interface(
|
291 |
fn=analyze_stock,
|
|
|
296 |
],
|
297 |
outputs=gr.Textbox(label="基本面分析結果"),
|
298 |
title="股票基本面分析 App",
|
299 |
+
description="輸入您的 LLM API key、股票代號與時間區間,取得該股票的分析報告(非 JSON、易讀文字段落)。"
|
300 |
)
|
301 |
|
302 |
if __name__ == "__main__":
|