|
import yfinance as yf |
|
import numpy as np |
|
from typing import Dict, Any |
|
from langchain_openai import ChatOpenAI |
|
from langchain_core.prompts import ChatPromptTemplate |
|
from langchain.schema import StrOutputParser |
|
from .state import AgentState |
|
import os |
|
|
|
from dotenv import load_dotenv |
|
load_dotenv() |
|
|
|
def get_portfolio_data(portfolio): |
|
"""Fetch historical data for portfolio assets.""" |
|
tickers = list(portfolio.keys()) |
|
|
|
|
|
historical_data = {} |
|
|
|
for ticker in tickers: |
|
try: |
|
ticker_obj = yf.Ticker(ticker) |
|
data = ticker_obj.history(period="1y") |
|
historical_data[ticker] = data |
|
except Exception as e: |
|
print(f"Error fetching data for {ticker}: {e}") |
|
|
|
return historical_data |
|
|
|
def calculate_portfolio_metrics(portfolio, historical_data): |
|
"""Calculate portfolio metrics.""" |
|
|
|
total_value = sum(portfolio[ticker]["value"] for ticker in portfolio) |
|
allocations = {ticker: portfolio[ticker]["value"] / total_value for ticker in portfolio} |
|
|
|
|
|
returns = {} |
|
volatility = {} |
|
for ticker in historical_data: |
|
if not historical_data[ticker].empty: |
|
price_data = historical_data[ticker]['Close'] |
|
daily_returns = price_data.pct_change().dropna() |
|
returns[ticker] = daily_returns.mean() * 252 |
|
volatility[ticker] = daily_returns.std() * np.sqrt(252) |
|
|
|
return { |
|
"total_value": total_value, |
|
"allocations": allocations, |
|
"returns": returns, |
|
"volatility": volatility |
|
} |
|
|
|
def create_portfolio_analyzer_chain(): |
|
"""Create a chain for portfolio analysis using LLM.""" |
|
|
|
prompt = ChatPromptTemplate.from_template(""" |
|
You are a portfolio analysis expert. Analyze the following portfolio information and provide insights. |
|
|
|
Portfolio Data: |
|
{portfolio_data} |
|
|
|
Portfolio Metrics: |
|
{portfolio_metrics} |
|
|
|
Fundamental Data: |
|
{fundamental_data} |
|
|
|
Risk Level: {risk_level} |
|
Investment Goals: {investment_goals} |
|
|
|
Provide a comprehensive analysis of the portfolio including: |
|
1. Overall portfolio composition and diversification |
|
2. Risk assessment based on volatility and beta |
|
3. Valuation assessment based on fundamental metrics |
|
4. Alignment with the user's risk level and investment goals |
|
5. Areas of concern or potential improvement |
|
|
|
Your analysis should be detailed, insightful, and actionable. |
|
""") |
|
openai_api_key = os.getenv("OPENAI_API_KEY") |
|
|
|
llm = ChatOpenAI(model="gpt-4-turbo", temperature=0.2, api_key=openai_api_key) |
|
|
|
|
|
chain = prompt | llm | StrOutputParser() |
|
|
|
return chain |
|
|
|
|
|
portfolio_analyzer_chain = create_portfolio_analyzer_chain() |
|
|
|
def portfolio_analyzer(state: AgentState) -> AgentState: |
|
"""Analyzes the current portfolio composition using LLM.""" |
|
portfolio = state["portfolio_data"] |
|
risk_level = state["risk_level"] |
|
investment_goals = state["investment_goals"] |
|
|
|
|
|
historical_data = get_portfolio_data(portfolio) |
|
|
|
|
|
portfolio_metrics = calculate_portfolio_metrics(portfolio, historical_data) |
|
|
|
|
|
portfolio_data_str = "" |
|
for ticker, data in portfolio.items(): |
|
portfolio_data_str += f"{ticker}: {data['shares']} shares at ${data['purchase_price']:.2f}, " |
|
portfolio_data_str += f"current value: ${data['value']:.2f}, " |
|
portfolio_data_str += f"gain/loss: {data['gain_loss_pct']:.2f}%\n" |
|
|
|
|
|
fundamental_data = {} |
|
if "technical_analysis" in state: |
|
for ticker, analysis in state["technical_analysis"].items(): |
|
if "fundamental_data" in analysis: |
|
fundamental_data[ticker] = analysis["fundamental_data"] |
|
|
|
|
|
fundamental_data_str = "" |
|
for ticker, data in fundamental_data.items(): |
|
fundamental_data_str += f"{ticker}:\n" |
|
for key, value in data.items(): |
|
if value is not None: |
|
fundamental_data_str += f" {key}: {value}\n" |
|
fundamental_data_str += "\n" |
|
|
|
|
|
portfolio_metrics_str = "" |
|
portfolio_metrics_str += f"Total Value: ${portfolio_metrics['total_value']:.2f}\n\n" |
|
|
|
portfolio_metrics_str += "Allocations:\n" |
|
for ticker, allocation in portfolio_metrics['allocations'].items(): |
|
portfolio_metrics_str += f" {ticker}: {allocation*100:.2f}%\n" |
|
|
|
portfolio_metrics_str += "\nAnnualized Returns:\n" |
|
for ticker, ret in portfolio_metrics['returns'].items(): |
|
portfolio_metrics_str += f" {ticker}: {ret*100:.2f}%\n" |
|
|
|
portfolio_metrics_str += "\nAnnualized Volatility:\n" |
|
for ticker, vol in portfolio_metrics['volatility'].items(): |
|
portfolio_metrics_str += f" {ticker}: {vol*100:.2f}%\n" |
|
|
|
|
|
analysis = portfolio_analyzer_chain.invoke({ |
|
"portfolio_data": portfolio_data_str, |
|
"portfolio_metrics": portfolio_metrics_str, |
|
"fundamental_data": fundamental_data_str, |
|
"risk_level": risk_level, |
|
"investment_goals": investment_goals |
|
}) |
|
|
|
|
|
state["portfolio_analysis"] = { |
|
"total_value": portfolio_metrics["total_value"], |
|
"allocations": portfolio_metrics["allocations"], |
|
"returns": portfolio_metrics["returns"], |
|
"volatility": portfolio_metrics["volatility"], |
|
"fundamental_data": fundamental_data, |
|
"llm_analysis": analysis |
|
} |
|
|
|
|
|
state["messages"] = state.get("messages", []) + [{ |
|
"role": "ai", |
|
"content": f"[PortfolioAnalyzer] I've analyzed your portfolio. Here are my findings:\n\n{analysis}" |
|
}] |
|
|
|
return state |
|
|