cardinal1 / agents /recommendation_engine.py
don-unagi's picture
add app
c90e00d
import os
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain.output_parsers.openai_functions import JsonOutputFunctionsParser
from typing import Dict, List
from .state import AgentState
def create_recommendation_engine():
"""Create a recommendation engine using LangChain."""
# Define the function schema for structured output
function_def = {
"name": "generate_recommendations",
"description": "Generate investment recommendations based on portfolio analysis",
"parameters": {
"type": "object",
"properties": {
"portfolio_summary": {
"type": "string",
"description": "Summary of the current portfolio status"
},
"recommendations": {
"type": "array",
"items": {
"type": "object",
"properties": {
"ticker": {
"type": "string",
"description": "Stock ticker symbol"
},
"action": {
"type": "string",
"enum": ["BUY", "SELL", "HOLD"],
"description": "Recommended action for this stock"
},
"reasoning": {
"type": "string",
"description": "Detailed reasoning for the recommendation"
},
"priority": {
"type": "integer",
"description": "Priority level (1-5, where 1 is highest priority)",
"minimum": 1,
"maximum": 5
}
},
"required": ["ticker", "action", "reasoning", "priority"]
}
},
"portfolio_strengths": {
"type": "array",
"items": {
"type": "string",
"description": "Key strength of the portfolio"
},
"description": "List of portfolio strengths"
},
"portfolio_weaknesses": {
"type": "array",
"items": {
"type": "string",
"description": "Key weakness or area for improvement in the portfolio"
},
"description": "List of portfolio weaknesses or areas for improvement"
},
"allocation_advice": {
"type": "string",
"description": "Advice on portfolio allocation and diversification"
},
"risk_assessment": {
"type": "string",
"description": "Assessment of portfolio risk relative to user's risk tolerance"
},
"final_report": {
"type": "string",
"description": "Comprehensive final report with all recommendations and analysis"
}
},
"required": ["portfolio_summary", "recommendations", "portfolio_strengths", "portfolio_weaknesses", "allocation_advice", "risk_assessment", "final_report"]
}
}
# Create prompt template
prompt = ChatPromptTemplate.from_template("""
You are an expert financial advisor. Based on the following information, provide personalized investment recommendations.
PORTFOLIO:
{portfolio}
RISK PROFILE:
{risk_level}
INVESTMENT GOALS:
{investment_goals}
TECHNICAL ANALYSIS:
{technical_analysis}
FUNDAMENTAL DATA:
{fundamental_data}
RELEVANT NEWS:
{news}
SECURITY ANALYSIS INSIGHTS (Graham & Dodd):
{rag_insights}
AGENT DISCUSSIONS:
{agent_discussions}
Provide comprehensive investment recommendations that incorporate:
1. Technical analysis signals (RSI, moving averages, etc.)
2. Fundamental analysis metrics (PE ratios, debt-to-equity, growth rates, etc.)
3. Recent news sentiment and impact
4. Value investing principles from Graham & Dodd's Security Analysis
5. Alignment with the user's risk profile and investment goals
For each holding, provide a clear BUY/SELL/HOLD recommendation with detailed reasoning and priority level.
IMPORTANT: For the reasoning of each recommendation, include a more detailed technical and fundamental analysis section:
- For technical analysis: Include specific insights about RSI levels, MACD signals, moving average crossovers, and what these indicators suggest about momentum and trend direction.
- For fundamental analysis: Include specific metrics like P/E ratio compared to industry average, debt-to-equity ratio, earnings growth, and what these metrics suggest about the company's valuation and financial health.
- Use technical jargon appropriately to demonstrate expertise while still being clear.
- Format the reasoning to clearly separate technical insights from fundamental insights.
Identify 3-5 key strengths and weaknesses of the current portfolio.
Assess the overall risk and provide allocation advice.
Remember that your recommendations will be presented to a busy executive who needs concise, actionable insights.
Focus on clarity and brevity in your explanations.
""")
# Create LLM
llm = ChatOpenAI(model="gpt-4-turbo", temperature=0.2)
# Create chain
chain = (
prompt
| llm.bind_functions(functions=[function_def], function_call="generate_recommendations")
| JsonOutputFunctionsParser()
)
return chain
# Initialize recommendation engine
recommendation_engine_chain = create_recommendation_engine()
def recommendation_engine(state: AgentState) -> AgentState:
"""Generates investment recommendations based on all analyses including RAG insights."""
portfolio = state["portfolio_data"]
risk_level = state["risk_level"]
investment_goals = state["investment_goals"]
tech_analysis = state.get("technical_analysis", {})
news = state.get("news_analysis", [])
rag_insights = state.get("rag_context", "No RAG insights available.")
messages = state.get("messages", [])
fundamental_analysis = state.get("fundamental_analysis", {})
# Extract fundamental data and RAG interpretations from technical analysis
fundamental_data = {}
rag_interpretations = {}
for ticker in tech_analysis:
# Extract fundamental data
if isinstance(tech_analysis[ticker], dict):
if 'fundamental_data' in tech_analysis[ticker]:
fundamental_data[ticker] = tech_analysis[ticker]['fundamental_data']
# Extract RAG interpretations
if 'rag_interpretation' in tech_analysis[ticker]:
rag_interpretations[ticker] = tech_analysis[ticker]['rag_interpretation']
# Combine RAG interpretations with portfolio-level insights
combined_rag_insights = rag_insights
if rag_interpretations:
combined_rag_insights += "\n\n## Stock-Specific Interpretations:\n\n"
for ticker, interpretation in rag_interpretations.items():
combined_rag_insights += f"### {ticker}:\n{interpretation}\n\n"
# Format agent discussions
agent_discussions = "\n\n".join([f"{msg['role']}: {msg['content']}" for msg in messages])
# Invoke recommendation engine
try:
result = recommendation_engine_chain.invoke({
"portfolio": str(portfolio),
"risk_level": risk_level,
"investment_goals": investment_goals,
"technical_analysis": str(tech_analysis),
"fundamental_data": str(fundamental_data),
"news": str(news),
"rag_insights": combined_rag_insights,
"agent_discussions": agent_discussions
})
# Ensure we have all required fields
if "recommendations" not in result or not result["recommendations"]:
# Generate default recommendations if none were provided
result["recommendations"] = []
for ticker in portfolio:
result["recommendations"].append({
"ticker": ticker,
"action": "HOLD",
"reasoning": "Default recommendation due to insufficient analysis data.",
"priority": 3
})
if "final_report" not in result or not result["final_report"]:
# Generate a default final report
result["final_report"] = f"""
# Investment Portfolio Analysis Report
## Portfolio Overview
Risk Level: {risk_level}
Investment Goals: {investment_goals}
## Key Recommendations
{', '.join([f"{rec['ticker']}: {rec['action']}" for rec in result.get("recommendations", [])])}
## Summary
This is a default report generated due to insufficient analysis data.
"""
# Ensure other fields exist
if "portfolio_strengths" not in result:
result["portfolio_strengths"] = ["Diversification across multiple assets"]
if "portfolio_weaknesses" not in result:
result["portfolio_weaknesses"] = ["Potential for improved sector allocation"]
if "allocation_advice" not in result:
result["allocation_advice"] = "Consider maintaining a balanced portfolio aligned with your risk tolerance."
if "risk_assessment" not in result:
result["risk_assessment"] = f"Your portfolio appears to be aligned with your {risk_level} risk tolerance."
if "portfolio_summary" not in result:
result["portfolio_summary"] = f"Portfolio with {len(portfolio)} assets analyzed."
except Exception as e:
# Handle any errors in the recommendation engine
print(f"Error in recommendation engine: {str(e)}")
result = {
"recommendations": [],
"final_report": f"Error generating recommendations: {str(e)}",
"portfolio_strengths": [],
"portfolio_weaknesses": [],
"allocation_advice": "",
"risk_assessment": "",
"portfolio_summary": "Error in analysis"
}
# Update state with recommendations and enhanced data
state["recommendations"] = result["recommendations"]
state["final_report"] = result["final_report"]
# Add new structured data to state
state["portfolio_strengths"] = result.get("portfolio_strengths", [])
state["portfolio_weaknesses"] = result.get("portfolio_weaknesses", [])
state["allocation_advice"] = result.get("allocation_advice", "")
state["risk_assessment"] = result.get("risk_assessment", "")
# Add message to communication
state["messages"] = state.get("messages", []) + [{
"role": "ai",
"content": f"[RecommendationEngine] I've generated the following recommendations:\n\n{result.get('portfolio_summary', '')}\n\n"
f"Risk Assessment: {result.get('risk_assessment', '')}\n\n"
f"Allocation Advice: {result.get('allocation_advice', '')}"
}]
return state