Spaces:
Running
Running
File size: 12,108 Bytes
0dbb99d 4593632 0dbb99d fb2652f 0dbb99d 76e906f 0dbb99d fb2652f 76e906f fb2652f 76e906f fb2652f 0dbb99d fb2652f 4593632 0dbb99d 76e906f 0dbb99d cdf1418 76e906f 0dbb99d cdf1418 0255349 0dbb99d cdf1418 0dbb99d 2154d91 0255349 2154d91 ee48331 4593632 ee48331 2154d91 4593632 2154d91 4593632 2154d91 76e906f 2154d91 ee48331 76e906f ee48331 2154d91 4593632 2154d91 0dbb99d ee48331 4593632 2154d91 4593632 2154d91 0dbb99d ee48331 0dbb99d 2154d91 4593632 2154d91 4593632 2154d91 0dbb99d ee48331 0dbb99d 2154d91 4593632 2154d91 4593632 ee48331 2154d91 0dbb99d 0255349 0dbb99d ee48331 0dbb99d 0255349 0dbb99d ee48331 0255349 0dbb99d 0255349 0dbb99d ee48331 fb2652f ee48331 fb2652f ee48331 fb2652f ee48331 0255349 fb2652f 0dbb99d 0255349 0dbb99d ee48331 0dbb99d 0255349 0dbb99d 4593632 0255349 0dbb99d 0255349 0dbb99d fb2652f 0dbb99d 76e906f 0dbb99d 76e906f 0dbb99d 7236baa 0dbb99d 7236baa 0dbb99d 76e906f 0255349 0dbb99d 0255349 fb2652f 0dbb99d 0255349 0dbb99d 6953dde 0dbb99d |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 |
import os
import datetime as dt
import yfinance as yf
import traceback
import json
from typing import Union, Dict
import gradio as gr
from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
from ta.momentum import RSIIndicator, StochasticOscillator
from ta.trend import MACD
from ta.volume import volume_weighted_average_price
import pandas as pd
import dotenv
dotenv.load_dotenv()
# --- 新版 Prompt(改為人類可讀的非 JSON 輸出) ---
FUNDAMENTAL_ANALYST_PROMPT = """
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.
You have access to the following tools:
1. **get_stock_prices**: Retrieves stock price data and technical indicators.
2. **get_financial_metrics**: Retrieves key financial metrics and financial ratios.
3. **get_financial_news**: Retrieves the latest financial news related to the stock.
4. **get_industry_data** *(if available)*: Retrieves industry trends and competitive positioning information.
---
### Your Task:
1. Use the provided stock symbol to query the tools.
2. Analyze the following areas in sequence:
- **Stock price movements and technical indicators**: Examine recent price trends, volatility, and signals from RSI, MACD, VWAP, and other indicators.
- **Financial health and key financial ratios**: Assess profitability, liquidity, solvency, and operational efficiency using metrics such as:
- Profitability Ratios: Gross Profit Margin, Net Profit Margin, Operating Profit Margin
- Liquidity Ratios: Current Ratio, Quick Ratio
- Solvency Ratios: Debt-to-Equity Ratio, Interest Coverage Ratio
- Efficiency Ratios: Inventory Turnover, Accounts Receivable Turnover
- Market Ratios: Price-to-Earnings Ratio (P/E), Price-to-Book Ratio (P/B)
- **Recent news and market sentiment**: Identify significant events or trends impacting the company's market perception.
- **Industry analysis**: Evaluate the industry’s growth trends, technological advancements, and regulatory environment. Identify how the industry is evolving and how it affects the target company.
- **Competitor analysis**: Compare the target company with key competitors in terms of market share, financial health, and growth potential.
3. Provide a concise and structured summary covering all sections, ensuring each area has actionable insights.
---
### Output Format: 以下請用繁體中文輸出,且以「易讀文字段落/清單」方式呈現 (非 JSON):
請根據以下結構,撰寫一份詳細的分析報告:
1. 【股票代碼】
- 說明:顯示本次分析所針對的股票代號。
2. 【股票價格趨勢與技術指標分析】
- 說明:闡述近期股價變化趨勢、波動情況,並搭配 RSI、MACD、VWAP 等技術指標,提供觀察與解讀。
3. 【技術指標分析與見解】
- 說明:進一步解釋 RSI、MACD 等所呈現的訊號,與可能的後續走勢。
4. 【財務分析】
- 4.1 獲利能力比率分析
- 4.2 流動性比率分析
- 4.3 償債能力比率分析
- 4.4 營運效率比率分析
- 4.5 市場表現比率分析
- 4.6 總結財務健康狀況
5. 【新聞分析】
- 說明:列出近期相關新聞及其對股價、企業形象可能產生的影響。
6. 【產業分析】
- 說明:討論產業趨勢、發展動能與可能風險。
7. 【競爭對手分析】
- 說明:比較主要競爭對手之優勢與劣勢,以及對目標公司的影響。
8. 【最終結論與投資建議】
- 說明:歸納整體分析結果,給出投資人應採取之行動或建議。
9. 【回答使用者問題】
- 針對「Should I buy this stock?」之回應,提出依據上述分析的結論性建議。
※ 請確保敘述完整、清晰、條理分明、方便一般讀者閱讀。同時避免使用 JSON 格式,改以段落或清單式敘述。
"""
def period_to_start_end(period_str: str):
"""
根據使用者選擇的時間區間,計算起始與結束日期。
"""
now = dt.datetime.now()
if period_str == "3mo":
start = now - dt.timedelta(weeks=13)
elif period_str == "6mo":
start = now - dt.timedelta(weeks=26)
elif period_str == "1yr":
start = now - dt.timedelta(weeks=52)
else:
# 若未指定或指定其他值,一律視為 3 個月
start = now - dt.timedelta(weeks=13)
return start, now
def get_stock_prices(ticker: str, period: str = "3mo") -> Union[Dict, str]:
"""Fetches historical stock price data and technical indicators for a given ticker."""
try:
start, end = period_to_start_end(period)
print(f"[get_stock_prices] Downloading data for ticker={ticker}, period={period}")
print(f"[get_stock_prices] Start={start}, End={end}")
data = yf.download(
ticker,
start=start,
end=end,
interval='1d'
)
print(f"[get_stock_prices] Fetched data shape={data.shape}")
if not data.empty:
print(f"[get_stock_prices] Head of data:\n{data.head()}")
else:
print("[get_stock_prices] No data returned from yfinance.")
if data.empty:
return {"error": f"No stock data found for {ticker}"}
# 如果是多層欄位,就平坦化
if data.columns.nlevels > 1:
data.columns = [col[0] for col in data.columns]
# 設定日期為索引 (DatetimeIndex)
data.index = pd.to_datetime(data.index)
# 保留一份副本來輸出給 LLM 用
df_for_output = data.copy().reset_index()
df_for_output["Date"] = df_for_output["Date"].astype(str)
# 另存一份給技術指標用 (確保是 DatetimeIndex)
df = data.copy()
# 指標只取最後 12 筆即可
n = min(12, len(df))
indicators = {}
# RSI
rsi_series = RSIIndicator(df['Close'], window=14).rsi().iloc[-n:]
indicators["RSI"] = {
str(date.date()): float(value)
for date, value in rsi_series.dropna().items()
}
# Stochastic
sto_series = StochasticOscillator(
df['High'],
df['Low'],
df['Close'],
window=14
).stoch().iloc[-n:]
indicators["Stochastic_Oscillator"] = {
str(date.date()): float(value)
for date, value in sto_series.dropna().items()
}
# MACD
macd = MACD(df['Close'])
macd_series = macd.macd().iloc[-n:]
indicators["MACD"] = {
str(date.date()): float(value)
for date, value in macd_series.dropna().items()
}
macd_signal_series = macd.macd_signal().iloc[-n:]
indicators["MACD_Signal"] = {
str(date.date()): float(value)
for date, value in macd_signal_series.dropna().items()
}
# VWAP
vwap_series = volume_weighted_average_price(
high=df['High'],
low=df['Low'],
close=df['Close'],
volume=df['Volume']
).iloc[-n:]
indicators["vwap"] = {
str(date.date()): float(value)
for date, value in vwap_series.dropna().items()
}
return {
"stock_price": df_for_output.to_dict(orient="records"),
"indicators": indicators
}
except Exception as e:
print(f"[get_stock_prices] Exception: {str(e)}")
return f"Error fetching price data: {str(e)}"
def get_financial_news(ticker: str) -> Union[Dict, str]:
"""Fetches the latest financial news related to a given ticker."""
try:
print(f"[get_financial_news] Fetching news for ticker={ticker}")
stock = yf.Ticker(ticker)
news = stock.news # 從 Yahoo Finance 獲取新聞
if not news:
print("[get_financial_news] No news data returned from yfinance.")
return {"news": "No recent news found."}
# 詳細列印前 5 則新聞資訊
latest_news = []
for i, item in enumerate(news[:5], start=1):
print(f"[get_financial_news] News item {i} RAW: {json.dumps(item, indent=2)}")
latest_news.append({
"title": item.get("title"),
"publisher": item.get("publisher"),
"link": item.get("link"),
"published_date": item.get("providerPublishTime")
})
print(f"[get_financial_news] Found {len(latest_news)} news items (detailed above).")
return {"news": latest_news}
except Exception as e:
print(f"[get_financial_news] Exception: {str(e)}")
return f"Error fetching news: {str(e)}"
def get_financial_metrics(ticker: str) -> Union[Dict, str]:
"""Fetches key financial ratios for a given ticker."""
try:
print(f"[get_financial_metrics] Fetching financial metrics for ticker={ticker}")
stock = yf.Ticker(ticker)
info = stock.info
print(f"[get_financial_metrics] Info returned:\n{json.dumps(info, indent=2)}")
return {
'pe_ratio': info.get('forwardPE'),
'price_to_book': info.get('priceToBook'),
'debt_to_equity': info.get('debtToEquity'),
'profit_margins': info.get('profitMargins')
}
except Exception as e:
print(f"[get_financial_metrics] Exception: {str(e)}")
return f"Error fetching ratios: {str(e)}"
def analyze_stock(api_key: str, ticker: str, period: str) -> str:
"""
根據輸入的 LLM API key、股票代號與時間區間,抓取各項資料後,
組合成分析 Prompt 呼叫 LLM,最後回傳『易讀文字段落』的基本面分析結果。
"""
try:
# 建立 LLM 實例
llm = ChatOpenAI(
model='gpt-4o',
openai_api_key=api_key,
temperature=0
)
# 取得資料
price_data = get_stock_prices(ticker, period)
metrics = get_financial_metrics(ticker)
news = get_financial_news(ticker)
# 組合 Prompt
prompt = FUNDAMENTAL_ANALYST_PROMPT.replace("{company}", ticker)
user_question = "Should I buy this stock?"
analysis_prompt = f"""
根據以下 {ticker} 的資料,進行全面的基本面分析並回答使用者問題:"{user_question}"
股價與技術指標資料:
{json.dumps(price_data, ensure_ascii=False, indent=2)}
財務指標:
{json.dumps(metrics, ensure_ascii=False, indent=2)}
相關新聞:
{json.dumps(news, ensure_ascii=False, indent=2)}
請注意:即使新聞的 description 或 summary 為空,也請務必根據新聞的 title、publisher 或其他字段,推斷其可能對股價與公司形象造成的影響。請將這些新聞具體列在新聞分析章節。
{prompt}
"""
print("[analyze_stock] Sending prompt to LLM...")
response = llm.invoke(analysis_prompt)
return response.content # 回傳「非 JSON」的易讀文字段落
except Exception as e:
print(f"[analyze_stock] Exception: {str(e)}")
return f"分析過程中發生錯誤: {str(e)}\n{traceback.format_exc()}"
# --- Gradio 介面 ---
iface = gr.Interface(
fn=analyze_stock,
inputs=[
gr.Textbox(label="LLM API Key", type="password", placeholder="請輸入 OpenAI API Key"),
gr.Textbox(label="股票代號", placeholder="例如:TSLA 或 2330.TW"),
gr.Dropdown(choices=["3mo", "6mo", "1yr"], label="時間區間", value="3mo")
],
outputs=gr.Textbox(label="基本面財務新聞方面分析結果"),
title="我應該買這支股票嗎? by DAVID888",
description="輸入您的 LLM openai key、股票代號與時間區間,取得該股票的分析報告"
)
if __name__ == "__main__":
iface.launch() |