Stock-Analyzer / app.py
tbdavid2019's picture
1
0dbb99d
raw
history blame
9.18 kB
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()