Spaces:
Running
Running
File size: 9,177 Bytes
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 |
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 dotenv
dotenv.load_dotenv()
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 : 以下請用繁體中文輸出
{
"stock": "",
"price_analysis": "<股票價格趨勢與技術指標分析>",
"technical_analysis": "<技術指標分析與見解>",
"financial_analysis": {
"profitability_ratios": "<獲利能力比率分析>",
"liquidity_ratios": "<流動性比率分析>",
"solvency_ratios": "<償債能力比率分析>",
"efficiency_ratios": "<營運效率比率分析>",
"market_ratios": "<市場表現比率分析>",
"summary": "<財務整體健康狀況與分析結論>"
},
"news_analysis": "<近期新聞摘要與其對股價的潛在影響>",
"industry_analysis": "<產業趨勢、成長動力與潛在風險>",
"competitor_analysis": "<主要競爭對手比較與市場地位分析>",
"final_summary": "<整體綜合結論與投資建議>",
"Asked Question Answer": "<根據上述分析的具體回答>"
}
"""
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:
start = now - dt.timedelta(weeks=13)
return start, now
# --- 取得股票價格與技術指標 ---
def get_stock_prices(ticker: str, period: str = "3mo") -> Union[Dict, str]:
"""
使用 start 與 end 取得歷史股價資料(避免連線到 fc.yahoo.com),
並計算 RSI、Stochastic、MACD 與 VWAP 指標。
"""
try:
start_date, end_date = period_to_start_end(period)
data = yf.download(
ticker,
start=start_date,
end=end_date,
interval='1d'
)
df = data.copy()
# 若有 multi-index,調整欄位名稱
if df.columns.nlevels > 1:
df.columns = [col[0] for col in df.columns]
data.reset_index(inplace=True)
data['Date'] = data['Date'].astype(str)
indicators = {}
# RSI
rsi_series = RSIIndicator(df['Close'], window=14).rsi().iloc[-12:]
indicators["RSI"] = {date.strftime('%Y-%m-%d'): int(value) for date, value in rsi_series.dropna().to_dict().items()}
# Stochastic Oscillator
sto_series = StochasticOscillator(df['High'], df['Low'], df['Close'], window=14).stoch().iloc[-12:]
indicators["Stochastic_Oscillator"] = {date.strftime('%Y-%m-%d'): int(value) for date, value in sto_series.dropna().to_dict().items()}
# MACD 與訊號線
macd = MACD(df['Close'])
macd_series = macd.macd().iloc[-12:]
indicators["MACD"] = {date.strftime('%Y-%m-%d'): int(value) for date, value in macd_series.to_dict().items()}
macd_signal_series = macd.macd_signal().iloc[-12:]
indicators["MACD_Signal"] = {date.strftime('%Y-%m-%d'): int(value) for date, value in macd_signal_series.to_dict().items()}
# VWAP
vwap_series = volume_weighted_average_price(
high=df['High'],
low=df['Low'],
close=df['Close'],
volume=df['Volume']
).iloc[-12:]
indicators["vwap"] = {date.strftime('%Y-%m-%d'): int(value) for date, value in vwap_series.to_dict().items()}
return {'stock_price': data.to_dict(orient='records'), 'indicators': indicators}
except Exception as e:
return f"Error fetching price data: {str(e)}"
# --- 取得財務新聞 ---
def get_financial_news(ticker: str) -> Union[Dict, str]:
try:
stock = yf.Ticker(ticker)
news = stock.news
if not news:
return {"news": "No recent news found."}
latest_news = [
{
"title": item.get('title'),
"publisher": item.get('publisher'),
"link": item.get('link'),
"published_date": item.get('providerPublishTime')
}
for item in news[:5]
]
return {"news": latest_news}
except Exception as e:
return f"Error fetching news: {str(e)}"
# --- 取得財務指標 ---
def get_financial_metrics(ticker: str) -> Union[Dict, str]:
try:
stock = yf.Ticker(ticker)
info = stock.info
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:
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)}
{prompt}
"""
# 呼叫 LLM 生成最終分析報告
response = llm.invoke(analysis_prompt)
return response.content
except Exception as 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="股票基本面分析 App",
description="輸入您的 LLM API key、股票代號與時間區間,取得該股票的基本面分析報告。"
)
if __name__ == "__main__":
iface.launch() |