Skip to content

Instantly share code, notes, and snippets.

@CraftsMan-Labs
Created February 28, 2025 16:30
Show Gist options
  • Save CraftsMan-Labs/f0375098f0bcf5458a3fa5df43a39f2e to your computer and use it in GitHub Desktop.
Save CraftsMan-Labs/f0375098f0bcf5458a3fa5df43a39f2e to your computer and use it in GitHub Desktop.

FinSphere-Inspired Conversational Stock Analysis Notebook

Introduction
Current financial LLMs often lack depth in stock analysis and struggle to incorporate up-to-date data. The FinSphere research proposes a solution by combining real-time data access, quantitative tools, and an LLM to produce professional-grade stock analysis ([2501.12399] FinSphere: A Conversational Stock Analysis Agent Equipped with Quantitative Tools based on Real-Time Database). This notebook demonstrates a simplified FinSphere-inspired pipeline for conversational stock analysis, integrating several components:

  • Pydantic for defining and validating structured request/response data models.
  • Pinecone as a vector database to store and retrieve relevant stock data (e.g. technical indicators, fundamentals, recent news).
  • LiteLLM for interacting with a language model to generate insights from the data.
  • A Structured Query Processing workflow that ties these components together to handle user queries in a systematic way.

(FinSphere: A Conversational Stock Analysis Agent Equipped with Quantitative Tools based on Real-Time Database) Figure: FinSphere agent workflow decomposes a query into sub-tasks, uses specialized tools (technical, fundamental analysis, etc.) on real-time data, and then an LLM (FinSphere) synthesizes a comprehensive response ([2501.12399] FinSphere: A Conversational Stock Analysis Agent Equipped with Quantitative Tools based on Real-Time Database) ([2501.12399] FinSphere: A Conversational Stock Analysis Agent Equipped with Quantitative Tools based on Real-Time Database).

The goal is to emulate FinSphere’s approach of breaking down a user’s stock query, fetching up-to-date analysis data, and responding with well-structured financial insights. The following sections will set up each part of the system and then demonstrate an example query processing, aligning with the principles of real-time data integration, structured analysis, and AI-assisted insights from the FinSphere paper.

1. Pydantic for Data Validation (Request/Response Models)

Using Pydantic, we define data models for the input query and the output analysis. This ensures the query has the required structure (e.g. stock symbol and query text), and the response is returned in a well-defined format. Enforcing a structured schema helps produce consistent, professional-style reports ([2501.12399] FinSphere: A Conversational Stock Analysis Agent Equipped with Quantitative Tools based on Real-Time Database) and catch any deviations early.

Define Pydantic models for a stock analysis request and response:

# If not already installed, install Pydantic (for data validation)
# !pip install pydantic

from pydantic import BaseModel

class StockAnalysisRequest(BaseModel):
    stock_symbol: str  # e.g. "AAPL" for Apple Inc.
    query: str         # The user's question or request about the stock

class StockAnalysisResponse(BaseModel):
    stock_symbol: str
    technical_analysis: str
    fundamental_analysis: str
    recommendation: str

Here, StockAnalysisRequest expects a stock ticker and a query string. StockAnalysisResponse is structured to include separate fields for technical analysis insights, fundamental analysis insights, and an overall recommendation. Pydantic will validate incoming data (e.g., ensuring required fields are present and of correct type) and can also be used to ensure the LLM’s output conforms to this schema.

2. Pinecone for Vector Database (Storage/Retrieval of Stock Data)

FinSphere uses real-time financial databases to supply the latest data to each analytical tool ([2501.12399] FinSphere: A Conversational Stock Analysis Agent Equipped with Quantitative Tools based on Real-Time Database). In our implementation, we use Pinecone as a cloud-hosted vector database to store and retrieve contextual data for stock analysis. This could include encoded text embeddings of financial statements, price trend indicators, or news snippets related to stocks. By querying Pinecone, the system can fetch relevant information to ground the LLM’s analysis in data.

Setup Pinecone connection and index: (Make sure to insert your Pinecone API key and environment)

# Install Pinecone client if needed
# !pip install pinecone-client

import pinecone

# Initialize Pinecone with your API credentials
pinecone.init(api_key="YOUR_PINECONE_API_KEY", environment="YOUR_PINECONE_ENV")

# Ensure the Pinecone index exists (using a small embedding dimension for demo)
index_name = "stock-analysis"
if index_name not in pinecone.list_indexes():
    pinecone.create_index(index_name, dimension=8)  # using dimension=8 for example
index = pinecone.Index(index_name)

In practice, the dimension should match the length of the vector embeddings you use for your data (often hundreds of dimensions when using models like OpenAI’s text-embedding-ada-002). Here we use a small dimension for simplicity.

Store example stock data in the index. Typically, you would embed textual data (like a summary of a company’s financials or technical indicators) into a vector and upsert it. For demonstration, we'll simulate this with dummy embeddings and metadata:

# Example data to store: technical and fundamental analysis snippets for two stocks
example_data = {
    "AAPL": {
        "technical": "AAPL price above 50-day MA, bullish short-term trend.",
        "fundamental": "AAPL Q3 earnings up 12% YoY, strong balance sheet."
    },
    "TSLA": {
        "technical": "TSLA below 20-day MA, high volatility observed.",
        "fundamental": "TSLA mixed earnings (revenue +5%, margins down)."
    }
}

# Simulate embedding the text (in practice, use an embedding model to get a real vector)
# Here we'll just use small static vectors for illustration.
import numpy as np
for stock, info in example_data.items():
    fake_vector = np.random.rand(8).tolist()  # placeholder 8-dim vector
    metadata = {"technical": info["technical"], "fundamental": info["fundamental"]}
    index.upsert([(stock, fake_vector, metadata)])

We upserted two items (AAPL and TSLA) with random 8-dimensional vectors and metadata containing analysis text. In a real scenario, you might periodically update these entries with the latest data so that the system always has a "real-time" knowledge base to draw from.

Query Pinecone for relevant data: Given a user query, we need to retrieve the most relevant context. FinSphere’s agent identifies which data (tool outputs) are needed for the query ([2501.12399] FinSphere: A Conversational Stock Analysis Agent Equipped with Quantitative Tools based on Real-Time Database). For simplicity, if the query contains a specific ticker, we’ll fetch that ticker’s data from Pinecone. Otherwise, one could use a semantic search by embedding the query and using index.query().

def retrieve_stock_data(query: str, symbol: str = None):
    """Retrieve relevant stock analysis data from Pinecone by stock symbol or query text."""
    if symbol:
        # If stock symbol is known, fetch by ID
        result = index.fetch(ids=[symbol])
        if result and result.get("matches"):
            metadata = result["matches"][0]["metadata"]
            return metadata  # a dict with 'technical' and 'fundamental'
    # Fallback: semantic search by query embedding (if symbol not provided)
    # Embed the query text to vector (placeholder since we don't have real embed here)
    query_vector = np.random.rand(8).tolist()
    response = index.query(vector=query_vector, top_k=1, include_metadata=True)
    if response["matches"]:
        return response["matches"][0]["metadata"]
    return None

# Example retrieval
data = retrieve_stock_data("What is the outlook for AAPL after its earnings?", symbol="AAPL")
print(data)

In the code above, retrieve_stock_data first tries to use the provided stock symbol to directly fetch the stored data. If no symbol is given, it would perform a vector similarity search using an embedding of the query (here we simulated the query embedding). The result is a dictionary of metadata (technical and fundamental analysis snippets) relevant to the query.

Output (example retrieval):

{'technical': 'AAPL price above 50-day MA, bullish short-term trend.',
 'fundamental': 'AAPL Q3 earnings up 12% YoY, strong balance sheet.'}

3. LiteLLM for Language Model Interaction

With relevant context in hand, the next step is to use an LLM to interpret the data and answer the user’s query. We utilize LiteLLM, a Python SDK that provides a unified API to call various LLMs (OpenAI, Anthropic, etc.) in an OpenAI-compatible format. This allows us to send a prompt composed of the user’s question and the retrieved data, and get a completion from an AI model.

Setup LiteLLM and API credentials:

# Install LiteLLM if needed
# !pip install litellm

import os
from litellm import completion

# Set the API key for your chosen provider (here using OpenAI as an example)
os.environ["OPENAI_API_KEY"] = "YOUR_OPENAI_API_KEY"

LiteLLM uses the environment variable (or config) for the API key. You can specify different providers/models; by default, if we call completion with model="gpt-3.5-turbo", it will route to the OpenAI API. Make sure to replace "YOUR_OPENAI_API_KEY" with a valid API key.

Compose a prompt and call the LLM: We will format a prompt that presents the retrieved technical and fundamental analysis to the LLM, then asks it to answer the user’s query based on that information. To enforce structured output (so we can easily validate with Pydantic), we will ask the LLM to respond in JSON with the fields matching our Pydantic response model.

def ask_llm_for_analysis(stock: str, technical_text: str, fundamental_text: str, user_question: str):
    # System prompt to guide the AI (optional but helps define role)
    system_msg = {"role": "system", "content": "You are an AI financial analyst. Provide a concise analysis."}
    # User prompt includes the data and the question, and instructs JSON output
    user_content = (
        f"Stock: {stock}\n"
        f"Technical Analysis: {technical_text}\n"
        f"Fundamental Analysis: {fundamental_text}\n\n"
        f"Question: {user_question}\n"
        f"Respond with a JSON object with keys: technical_analysis, fundamental_analysis, recommendation."
    )
    user_msg = {"role": "user", "content": user_content}
    # Call the LLM via LiteLLM
    response = completion(model="gpt-3.5-turbo", messages=[system_msg, user_msg])
    # Extract the assistant's reply text
    reply_text = response["choices"][0]["message"]["content"]
    return reply_text

# Example LLM prompt (not executed here, just illustration)
stock = "AAPL"
tech = "AAPL price above 50-day MA, bullish short-term trend."
fund = "AAPL Q3 earnings up 12% YoY, strong balance sheet."
question = "What is the outlook for AAPL after its latest earnings?"
prompt = f"Stock: {stock}\\nTechnical Analysis: {tech}\\nFundamental Analysis: {fund}\\nQuestion: {question}\\nRespond with JSON."
print(prompt)

In the prompt, we feed the LLM the context for AAPL: a technical analysis snippet and a fundamental analysis snippet, then we ask the question. We explicitly instruct the model to output a JSON object with the fields we want. This leverages the LLM to synthesize a coherent answer from the pieces of data. (In FinSphere, the instruction-tuned model takes all tool outputs and generates a cohesive report ([2501.12399] FinSphere: A Conversational Stock Analysis Agent Equipped with Quantitative Tools based on Real-Time Database) in a similar manner.)

Example prompt content printed above would look like:

Stock: AAPL  
Technical Analysis: AAPL price above 50-day MA, bullish short-term trend.  
Fundamental Analysis: AAPL Q3 earnings up 12% YoY, strong balance sheet.  

Question: What is the outlook for AAPL after its latest earnings?  
Respond with a JSON object with keys: technical_analysis, fundamental_analysis, recommendation.

When the completion call is executed, the LLM will (ideally) return a JSON string. For example, it might return:

{
  "technical_analysis": "Apple’s stock has maintained an uptrend, trading above its 50-day moving average, indicating bullish momentum.",
  "fundamental_analysis": "The company’s recent earnings were strong, with a 12% year-over-year increase in revenue and a solid balance sheet, reflecting robust fundamentals.",
  "recommendation": "Given the positive technical indicators and strong earnings, the outlook for AAPL is favorable. Long-term investors could consider this a buy, though they should remain aware of market volatility."
}

(This is a sample of the kind of structured answer we expect the LLM to produce.)

4. Structured Query Processing Pipeline

Now we put it all together. This section implements the end-to-end flow: validate input with Pydantic, retrieve relevant data from Pinecone, get the LLM’s analysis via LiteLLM, and finally validate/structure the output into our Pydantic response model. This modular design ensures each step is handled cleanly – aligning with FinSphere’s multi-stage workflow of using specialized tools then synthesis ([2501.12399] FinSphere: A Conversational Stock Analysis Agent Equipped with Quantitative Tools based on Real-Time Database).

import json

def process_stock_query(request: StockAnalysisRequest) -> StockAnalysisResponse:
    """Process a stock analysis query through the pipeline and return a structured response."""
    # 1. Data Retrieval from Pinecone
    stock = request.stock_symbol
    data = retrieve_stock_data(request.query, symbol=stock)
    if data is None:
        # If no data found, respond accordingly
        return StockAnalysisResponse(
            stock_symbol=stock,
            technical_analysis="No data available for the requested stock.",
            fundamental_analysis="No data available for the requested stock.",
            recommendation="Unable to provide analysis without relevant data."
        )
    tech_text = data.get("technical", "")
    fund_text = data.get("fundamental", "")
    
    # 2. LLM Analysis via LiteLLM
    llm_reply = ask_llm_for_analysis(stock, tech_text, fund_text, request.query)
    # The LLM is instructed to return JSON. Parse it:
    try:
        analysis_json = json.loads(llm_reply.strip())
    except json.JSONDecodeError:
        # If the LLM output isn’t valid JSON, we handle it (could ask LLM to retry, etc.)
        analysis_json = {
            "technical_analysis": tech_text or "",
            "fundamental_analysis": fund_text or "",
            "recommendation": llm_reply.strip()  # fallback: use the raw LLM reply as recommendation
        }
    
    # 3. Validate and structure the response using Pydantic
    response = StockAnalysisResponse(
        stock_symbol = stock,
        technical_analysis = analysis_json.get("technical_analysis", ""),
        fundamental_analysis = analysis_json.get("fundamental_analysis", ""),
        recommendation = analysis_json.get("recommendation", "")
    )
    return response

Steps in process_stock_query:

  • Retrieve data: We fetch the relevant technical and fundamental info for the stock from Pinecone.
  • LLM call: We send the data and query to the LLM (via LiteLLM) and expect a JSON string response. We then parse the JSON. In a robust system, you might include error handling or even use Pydantic’s BaseModel.parse_obj for validation. Here we manually handle a JSON parsing fallback.
  • Pydantic validation: Finally, we construct a StockAnalysisResponse object. Pydantic will ensure the fields are correctly populated (and can enforce types/constraints). This is our structured result ready to return to the user or downstream systems.

5. Example Queries and Expected Responses

Let’s test the pipeline with an example query to see everything in action. We will create a sample request and run process_stock_query to obtain the structured analysis response.

# Example usage of the pipeline:
request_data = {"stock_symbol": "AAPL", "query": "What is the outlook for AAPL after its latest earnings?"}
request_obj = StockAnalysisRequest(**request_data)   # Validate input structure
response_obj = process_stock_query(request_obj)      # Process the query through the pipeline

# Output the response in structured (JSON) form
print(json.dumps(response_obj.model_dump(), indent=2))

Expected output (the structure and content will resemble the following):

{
  "stock_symbol": "AAPL",
  "technical_analysis": "Apple’s stock has maintained an uptrend, trading above its 50-day moving average, indicating bullish momentum.",
  "fundamental_analysis": "The company’s recent earnings were strong, with a 12% year-over-year increase in revenue and a solid balance sheet, reflecting robust fundamentals.",
  "recommendation": "Given the positive technical indicators and strong earnings, the outlook for AAPL is favorable. Long-term investors could consider this a buy, though they should remain aware of market volatility."
}

This JSON-formatted response, validated by our Pydantic model, provides a clear and structured analysis:

  • technical_analysis – A summary of recent price trend and technical indicator status.
  • fundamental_analysis – Highlights from the latest earnings and financial health.
  • recommendation – A concise insight or action point based on the data (buy/sell/hold or general outlook).

The final result demonstrates real-time data utilization and structured AI reasoning: we fetched up-to-date analysis data (simulated via Pinecone) and used an LLM to transform it into a coherent answer. This reflects FinSphere’s core idea of integrating quantitative tools with an LLM to enhance the depth and quality of stock analysis ([2501.12399] FinSphere: A Conversational Stock Analysis Agent Equipped with Quantitative Tools based on Real-Time Database) ([2501.12399] FinSphere: A Conversational Stock Analysis Agent Equipped with Quantitative Tools based on Real-Time Database). By following this notebook, you can build upon this framework to incorporate additional data sources, more complex analysis tools, or connect to live financial APIs, ultimately creating a conversational agent that provides insightful and data-driven stock analysis in real time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment