Spaces:
Running
Running
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() |