Building a Financial Analyst Agent with LangGraph and OpenAI

Introduction

In the world of stock trading, investors rely on various tools and methods to make informed decisions. Fundamental analysis is a common approach that provides actionable insights by assessing a company’s financial health and stock performance. With advancements in artificial intelligence and machine learning, stock analysis can now be highly automated. In this article, we will explore how to create a stock performance analysis Agent using LangChain, LangGraph, and Yahoo Finance, leveraging real-time stock data and key technical indicators.

Whether you’re a financial enthusiast, a developer, or a data scientist, this step-by-step tutorial will help you create your own intelligent agent. Let’s dive in!

What Will This Autonomous Financial Analyst Agent Do?

  • Fetch stock price data using Yahoo Finance.
  • Calculate technical indicators such as RSI, MACD, VWAP, etc..
  • Evaluate financial metrics like P/E ratio, debt-to-equity ratio, and profit margins.
  • Utilize OpenAI’s powerful language model to provide structured, AI-based analysis.

Tools We Will Use

  1. LangGraph: A library for orchestrating tools and building conversational agents.
  2. OpenAI GPT-4: Used to generate intelligent and structured financial insights.
  3. yfinance: For retrieving stock prices and financial ratios.
  4. ta (Technical Analysis Library): For calculating key technical indicators.
  5. Python Libraries: pandas, dotenv, and datetime for data manipulation and environment setup.

Step 1: Set Up the Environment

First, install the required libraries:

pip install -U langgraph langchain langchain_openai pandas ta python-dotenv yfinance

Set up a <span>.env</span> file to securely store your OpenAI API key:

OPENAI_API_KEY=your_openai_api_key

Step 2: Prepare Tools for the Analyst

Fetch Stock Prices

This tool fetches historical data for a stock and calculates several technical indicators.

from typing import Union, Dict, Set, List, TypedDict, Annotated
import pandas as pd
from langchain_core.tools import tool
import yfinance as yf
from ta.momentum import RSIIndicator, StochasticOscillator
from ta.trend import SMAIndicator, EMAIndicator, MACD
from ta.volume import volume_weighted_average_price
import datetime as dt

@tool
def get_stock_prices(ticker: str) -> Union[Dict, str]:
    """
    Fetch historical price data and all technical indicators for a given stock.
    """
    try:
        data = yf.download(
            ticker,
            start=dt.datetime.now() - dt.timedelta(weeks=24*3),
            end=dt.datetime.now(),
            interval='1wk'
        )
        df = data.copy()
        data.reset_index(inplace=True)
        data.Date = data.Date.astype(str)
        indicators = {}
        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()}

        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(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_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)}"

Fetch Financial Metrics

This tool retrieves key financial health ratios.

@tool
def get_financial_metrics(ticker: str) -> Union[Dict, str]:
    """
    Fetch key financial ratios for a given stock.
    """
    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)}"

Step 3: Build LangGraph

LangGraph allows us to efficiently orchestrate tools and manage conversational logic.

1. Define the Graph

We first define a StateGraph to manage the workflow:

from langgraph.graph import StateGraph, START, END
from langchain_core.messages import SystemMessage
from langchain_core.messages import HumanMessage
from langchain_core.messages import AIMessage

class State(TypedDict):
    messages: List
    stock: str
graph_builder = StateGraph(State)

2. Define OpenAI and Bind Tools

We will integrate the tools into LangGraph and create a feedback loop for analysis.

import dotenv
dotenv.load_dotenv()
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model='gpt-4')
tools = [get_stock_prices, get_financial_metrics]
llm_with_tools = llm.bind_tools(tools)

3. Analyst Node

The prompt ensures AI understands its role and provides structured output.

FUNDAMENTAL_ANALYST_PROMPT = """  You are a fundamental analyst specializing in evaluating companies (with stock code {company}).
You have access to the following tools:
Medium Q Search
### Your Task:
1. ** Input Stock Code **: Use the provided stock code to query tools and fetch data.
2. ** Analyze Data **: Evaluate the results returned by the tools and identify potential trends.
3. ** Provide Summary **: Write a concise, well-structured summary highlighting:
- Recent stock price trends, patterns, and potential resistance levels.
- Key insights from technical indicators (e.g., whether the stock is overbought or oversold).
- Financial health and performance based on financial metrics.
### Constraints:
- Only use data provided by the tools.
- Avoid speculative language; focus on observable data and trends.
- If any tool fails to provide data, clearly state this in the summary.
### Output Format:
Respond in the following format: "stock": "<stock_code>",
"price_analysis": "<detailed analysis of stock price trends>",
"technical_analysis": "<detailed time series analysis of all technical indicators>",
"financial_analysis": "<detailed analysis of financial metrics>",
"final Summary": "<complete conclusion based on the above analysis>"
Please ensure your response is objective, concise, and actionable.
"""

def fundamental_analyst(state: State):
    messages = [
        SystemMessage(content=FUNDAMENTAL_ANALYST_PROMPT.format(company=state['stock'])),
        HumanMessage(content="Please analyze this stock.")
    ] + state['messages']
    return {'messages': llm_with_tools.invoke(messages)}

4. Add Tools to the Graph and Compile

graph_builder.add_node('fundamental_analyst', fundamental_analyst)
graph_builder.add_edge(START, 'fundamental_analyst')
graph_builder.add_node(ToolNode(tools))
graph_builder.add_conditional_edges('fundamental_analyst', tools_condition)
graph_builder.add_edge('tools', 'fundamental_analyst')
graph = graph_builder.compile()

5. Execute the Graph

events = graph.stream({'messages': [('user', 'Should I buy this stock?')], 'stock': 'TSLA'}, stream_mode='values')
for event in events:
    if 'messages' in event:
        event['messages'][-1].pretty_print()

Example Output

{
  "stock": "TSLA",
  "price_analysis": "Recent stock price of TSLA shows volatility,...",
  "technical_analysis": "Technical indicators present a mixed outlook. RSI indicates overbought condition,...",
  "financial_analysis": "TSLA's financial metrics indicate high valuation,...",
  "final Summary": "In conclusion, TSLA shows strong recent price recovery potential but also has high valuation and overbought indicators,...",
  "Asked Question Answer": "Given the current overbought indicators and high valuation,..."
}

Full code can be found in the community:Building a Financial Analyst Agent with LangGraph and OpenAI

Leave a Comment