Building an Agentic Stock Analysis Tool with LangChain, OpenBB, and Claude 3 Opus

Introduction

“The market does not beat them. They beat themselves, because though they have brains they cannot sit tight.” – Jesse Livermore

In this series of posts, we’ll explore how to build an AI-powered stock analysis tool using LangChain, OpenBB, and Anthropic’s latest, most powerful large language model, Claude 3 Opus. By leveraging these cutting-edge technologies, we can create a powerful application that provides traders and investors valuable insights and recommendations for making informed investment decisions.

According to Anthropic’s own press release, a strong potential use case for Opus involves the strategic “advanced analysis of charts & graphs, financials & market trends, [and] forecasting.” Let’s put that big brain to work for us, shall we?

Architecture Overview

The key components of the AI-powered stock analysis agent tool include:

  1. LangChain AgentExecutor: Orchestrates the AI workflow and coordinates the execution of tools and chains.
  2. OpenBB Tools: Custom tools built using OpenBB’s data and analysis capabilities for fetching and analyzing financial data.
  3. Anthropic’s Opus Language Model: Powers the AI agent, interpreting user queries and generating insightful responses.
  4. FastAPI Server: Exposes the AI-powered stock analysis tool as a web service. We will use Langserve here for simplicity and to get up and running quickly.

Code Walkthrough

Let’s dive into the code and explore the main components in detail. I will only focus on highlights in this post, but please feel free to access the code on GitHub. And if you have any questions, then definitely reach out.

  • AgentExecutor Setup:
Python
llm = ChatAnthropic(temperature=0, model_name="claude-3-opus-20240229")

# we will discuss tools and prompting below

agent = create_xml_agent(llm, tools, prompt)

class AgentInput(BaseModel):
    input: str
    chat_history: List[Tuple[str, str]] = Field(default_factory=list)

agent_executor = AgentExecutor.from_agent_and_tools(
    agent=agent,
    tools=tools,
    verbose=True,
).with_types(input_type=AgentInput) | (lambda x: x["output"])

The AgentExecutor is set up using the create_xml_agent function, passing in the Opus language model, tools, and a prompt that we will discuss in a bit. The executor is configured with the agent and tools, and the input type is specified as a Pydantic object. There are several key reasons to use XML for prompting with Claude. We use LangChain’s XML Agent to help us construct our agentic pipeline.

Prompt Template:

Python
HUMAN_TEMPLATE = """
You are an AI financial advisor with advanced knowledge of strategies for trading and investing.
You are enhanced with the capability to request and analyze technical and fundamental data of stocks. 
When users inquire about a stock's performance or history, you can offer insights into the stock's performance, 
trends, quantitative statistics, volatility, and market behavior.

You have access to the following tools:

{tools}

When accessing your tools, you may only use each tool once per user query. This is very important.

In order to use a tool, you can use <tool></tool> and <tool_input></tool_input> tags. You will then get back a response in the form <observation></observation>

For example, if you have a tool called 'search' that could run a google search, in order to search for the weather in SF you would respond:

<tool>search</tool><tool_input>weather in SF</tool_input>

<observation>64 degrees</observation>

When you are done, respond with a final answer between <final_answer></final_answer>. For example:

<final_answer>The weather in SF is 64 degrees</final_answer>

Rules for bullish setups:
1. Stock's last price is greater than its 20 SMA.
2. Stock's last price is greater than its 50 SMA.
3. Stock's last price is greater than its 200 SMA.
4. Stock's 50 SMA is greater than its 200 SMA.

Before processing the query, I will preprocess it as follows:
1. Correct any spelling errors using a spell checker or fuzzy matching technique.
2. If the stock symbol or company name is a partial match, find the closest matching stock symbol or company name.

Begin!

Previous Conversation:

{chat_history}

Question: {input}
{agent_scratchpad}"""

prompt = ChatPromptTemplate.from_template(HUMAN_TEMPLATE)

I have tailored Harrison’s excellent base prompt for working with XML agents to our use case. Specifically, the HUMAN_TEMPLATE defines the overall behavior and capabilities of the AI agent, as well as any specific rules and patterns for analyzing stocks.

We can employ a simple yet effective technique using moving averages to determine if a stock is in an uptrend. A moving average is a technical analysis tool that smooths out price data by calculating the average price over a specified number of periods. In this case, we will focus on the 20-day, 50-day, and 200-day simple moving averages (SMAs).

We can identify a stock in an early or established uptrend when its current price exceeds all three SMAs: the 20-day, 50-day, and 200-day. Moreover, the 50-day SMA must be greater than the 200-day SMA. Traders often refer to this condition as “stacked moving averages” or “bullish alignment.” In other words, shorter-term moving averages above longer-term ones indicate positive price momentum for the stock, suggesting a potential buying opportunity.

By employing this technique, traders can filter out non-trending stocks and concentrate on those displaying bullish characteristics. As a result, this approach streamlines the stock universe, making it more manageable and relevant, ultimately saving traders time and effort in their analysis. However, it’s crucial to recognize that stacked moving averages represent just one of many techniques or “edges” available to traders. Therefore, to make well-informed trading decisions, traders should combine this technique with other forms of analysis, such as fundamental analysis, chart patterns, or momentum indicators, as we will demonstrate here and in future posts.

OpenBB Tools

Python
class StockStatsInput(BaseModel):
    symbol: str = Field(..., description="Stock symbol to fetch data for")

@tool(args_schema=StockStatsInput)
def get_stock_stats(symbol: str) -> str:
    """Get Stock History and Statistics by Symbol."""

    try:
        start_date = (datetime.now() - timedelta(days=365 * 2)).strftime("%Y-%m-%d")
        df = obb.equity.price.historical(
            symbol, start_date=start_date, provider="yfinance"
        ).to_df()
        df.index = pd.to_datetime(df.index)

        if df.empty:
            return f"No data found for the given symbol {symbol}"

        stock_ret = qs.utils.download_returns(symbol, period=df.index)
        bench_ret = qs.utils.download_returns("^GSPC", period=df.index)
        stats = qs.reports.metrics(
            stock_ret, mode="full", benchmark=bench_ret, display=False
        )

        df = add_technicals(df, symbol=symbol)
        df = df[-30:][::-1]

        return f"Stats for {symbol}:\n{stats}\n\n{df.to_string(index=False)}"
    except Exception as e:
        return f"Error: {e}"

The get_stock_stats function is an example of a custom LangChain tool created using OpenBB. It fetches historical stock data and calculates various statistics using the quantstats library. The resulting statistics and pandas DataFrame are returned as a formatted string to our agent for processing.

Let’s add a few more custom OpenBB tools and Tavily Search.

Python
@tool
def get_gainers() -> str:
    """Get Top Gainers."""

    try:
        gainers = obb.equity.discovery.gainers(sort="desc").to_df()

        if gainers.empty:
            return "No gainers found"

        return f"Top Gainers:\n{gainers.to_string(index=False)}"
    except Exception as e:
        return f"Error: {e}"


@tool
def get_losers() -> str:
    """Get Top Losers."""

    try:
        losers = obb.equity.discovery.losers(sort="desc").to_df()

        if losers.empty:
            return "No losers found"

        return f"Top Losers:\n{losers.to_string(index=False)}"
    except Exception as e:
        return f"Error: {e}"

tavily = TavilySearchResults(max_results=1)

tools = [
    tavily,
    get_stock_stats,
    get_gainers,
    get_losers,
]

The get_gainers and get_losers tools allow the AI agent to quickly identify and retrieve the top gaining and losing stocks for a given market session, providing valuable context on the day’s most significant price movements.

FastAPI Server

Python
app = FastAPI(
    title="Financial Chat 2000",
    version="1.0",
    description="The finance dude abides",
)

@app.get("/")
async def redirect_root_to_docs():
    return RedirectResponse("/docs")

add_routes(
    app,
    agent_executor,
    path="/agent",
)

The FastAPI server has a root endpoint that redirects to the Swagger documentation. The AgentExecutor is added as a route using the add_routes function from Langserve, mapping it to the /agent endpoint. Here is a sample of the output of what we will build up to over this series of posts.

Wrapping Up

This post showcases the remarkable potential of integrating innovative technologies like LangChain, OpenBB, and Claude 3 Opus. By leveraging these tools, we can create powerful AI-driven stock analysis applications that provide users with deep insights and data-driven recommendations. This empowers traders and investors to make informed decisions.

We’ve delved into the high-level crucial components of our AI-powered stock analysis tool. These include the AgentExecutor setup, prompt template, custom OpenBB tools, and FastAPI server integration.

As you continue exploring and developing your own agent, consider incorporating additional data sources, adding more edges as you grow as a trader, and refining the AI agent’s prompts and responses. Optimizing performance for real-time analysis is also crucial. The intersection of LangChain, OpenBB, and Anthropic creates exciting possibilities and establishes a solid foundation for developing sophisticated AI-powered financial applications.

The potential for AI-driven stock analysis is genuinely captivating. By harnessing the right tools and technologies, we can keep pushing boundaries. As we refine our techniques and expand capabilities, the future of AI in finance appears exceptionally promising. This paves the way for more precise, insightful, and lucrative investment strategies. We will explore this and other topics as we continue swapping symbols.