How to Create Custom Analyst Agents for the AI Hedge Fund Trading System

To create custom analyst agents in the AI Hedge Fund system, implement an agent function with the signature (state: AgentState, agent_id: str), register it in the ANALYST_CONFIG dictionary in src/utils/analysts.py, and the LangGraph workflow will automatically incorporate your new node.

The AI Hedge Fund trading system uses a modular architecture where analyst agents generate trading signals that portfolio managers consume. When you create custom analyst agents, you extend the system's analytical capabilities by adding new nodes to the LangGraph workflow defined in the backend services.

Understanding the Analyst Architecture

The ANALYST_CONFIG Registry (src/utils/analysts.py)

The system maintains a single source of truth for all analyst agents in the ANALYST_CONFIG dictionary located in src/utils/analysts.py. This registry maps agent keys to metadata including display_name, description, investing_style, and the critical agent_func pointer that references your implementation.

When the backend graph is created in app/backend/services/graph.py, each entry in ANALYST_CONFIG is transformed into a LangGraph node via the create_agent_function wrapper implemented in app/backend/services/agent_service.py.

Agent Function Signature and State Management

Every custom analyst must implement a function with the exact signature:

def custom_agent(state: AgentState, agent_id: str = "custom_agent") -> dict:

The function receives:

  • state: An AgentState dictionary containing data (shared workspace) and metadata (execution flags)
  • agent_id: A unique string identifier injected by the graph builder

Your agent must write its trading signals to state["data"]["analyst_signals"][agent_id] using this structure:

{
    "TICKER": {
        "signal": "bullish" | "bearish" | "neutral",
        "confidence": int,  # 0-100

        "reasoning": str
    }
}

The function must return a standard LangGraph output dictionary:

return {"messages": [HumanMessage(content=json.dumps(results), name=agent_id)], "data": state["data"]}

Graph Construction (app/backend/services/graph.py)

The graph builder automatically constructs nodes for every entry in ANALYST_CONFIG:

analyst_nodes = {key: (f"{key}_agent", config["agent_func"])
                 for key, config in ANALYST_CONFIG.items()}

The order field in your config controls the default execution sequence and the visual ordering in the React Flow editor. The graph uses this ordering only for visualization and initial edge creation; actual execution follows the connections you draw in the front-end flow editor.

Step-by-Step Implementation Guide

Step 1: Create the Agent Function

Create a new Python file in src/agents/ following the naming convention of existing agents (e.g., custom_alpha.py). Implement your analysis logic using the required signature:

from src.graph.state import AgentState, show_agent_reasoning
from src.utils.llm import call_llm
from src.utils.progress import progress
from langchain_core.messages import HumanMessage
from pydantic import BaseModel, Field
from typing import Literal
import json

class CustomAlphaSignal(BaseModel):
    signal: Literal["bullish", "bearish", "neutral"]
    confidence: int = Field(description="Confidence 0-100")
    reasoning: str = Field(description="Brief justification")

def custom_alpha_agent(state: AgentState, agent_id: str = "custom_alpha_agent"):
    """Analyst that scores tickers based on EPS growth momentum."""
    data = state["data"]
    tickers = data["tickers"]
    
    results = {}
    
    for ticker in tickers:
        progress.update_status(agent_id, ticker, "Analyzing EPS growth")
        
        # Replace with actual data fetching logic

        eps_growth = 0.12  # Placeholder: 12% growth

        
        if eps_growth > 0.10:
            signal, confidence = "bullish", 80
        elif eps_growth < 0.02:
            signal, confidence = "bearish", 70
        else:
            signal, confidence = "neutral", 50
            
        reasoning = f"EPS growth of {eps_growth:.0%} indicates {'strong' if signal == 'bullish' else 'weak' if signal == 'bearish' else 'stable'} momentum"
        
        results[ticker] = {
            "signal": signal,
            "confidence": confidence,
            "reasoning": reasoning
        }
        
        progress.update_status(agent_id, ticker, "Done", analysis=json.dumps(results[ticker]))
    
    # Create message for LangGraph

    message = HumanMessage(content=json.dumps(results), name=agent_id)
    
    # Show reasoning if debug flag is set

    if state["metadata"].get("show_reasoning"):
        show_agent_reasoning(results, "Custom Alpha Analyst")
    
    # Store signals in shared state

    state["data"]["analyst_signals"][agent_id] = results
    progress.update_status(agent_id, None, "Done")
    
    return {"messages": [message], "data": state["data"]}

Step 2: Register in ANALYST_CONFIG

Import your function and add a new entry to the dictionary in src/utils/analysts.py:

from src.agents.custom_alpha import custom_alpha_agent

ANALYST_CONFIG = {
    # ... existing analysts (warren_buffett, valuation, etc.) ...

    
    "custom_alpha": {
        "display_name": "Custom Alpha Analyst",
        "description": "Rule-based analyst scoring tickers on EPS growth momentum",
        "investing_style": "Momentum / Growth",
        "agent_func": custom_alpha_agent,
        "type": "analyst",
        "order": 17,  # Controls UI ordering; use value after existing analysts

    },
}

The order field determines where your analyst appears in the default execution sequence and the React Flow editor. Use a unique integer higher than existing analysts to append to the end, or insert between existing values to position earlier.

Step 3: Deploy and Connect via UI

Once you register the agent and restart the backend, the system automatically exposes your analyst in the flow editor:

  1. Restart the backend – The graph builder in app/backend/services/graph.py reads the updated ANALYST_CONFIG on initialization.
  2. Open the flow editor – Navigate to the React Flow interface in app/frontend.
  3. Drag your node – The Custom Alpha Analyst appears in the sidebar using the display_name you configured.
  4. Wire connections – Connect your analyst node to portfolio managers or other analysts as needed. The graph will route signals from your agent's agent_id to downstream consumers.
  5. Save the flow – The backend persists the edge configuration and rebuilds the LangGraph workflow with your new node included.

Key Files for Custom Analyst Development

File Purpose
src/utils/analysts.py Central registry containing ANALYST_CONFIG dictionary where all analysts are defined.
src/agents/*.py Individual agent implementations (e.g., warren_buffett.py, valuation.py, and your new files).
app/backend/services/graph.py Constructs the LangGraph workflow by iterating over ANALYST_CONFIG and creating nodes.
app/backend/services/agent_service.py Provides create_agent_function wrapper that injects unique agent_id values into agent calls.

These four locations are the only touchpoints required to create custom analyst agents. The rest of the system—including portfolio management, risk controls, and backtesting—automatically consumes signals from any analyst registered in the config.

Summary

  • Register in ANALYST_CONFIG: Add your agent's metadata and function pointer to src/utils/analysts.py to make it discoverable by the graph builder.
  • Implement the standard signature: Your function must accept (state: AgentState, agent_id: str) and return {"messages": [...], "data": state["data"]} while writing signals to state["data"]["analyst_signals"].
  • Use the wrapper: The system automatically wraps your function via create_agent_function in app/backend/services/agent_service.py to inject unique IDs.
  • Deploy via UI: After restarting the backend, drag your new analyst node from the sidebar in the React Flow editor and wire it to portfolio managers or other agents.

Frequently Asked Questions

What is the required function signature for a custom analyst agent?

Your agent function must implement the signature (state: AgentState, agent_id: str) -> dict. The state parameter contains the shared AgentState dictionary with data (including tickers and analyst_signals) and metadata. The agent_id is injected by the create_agent_function wrapper. You must return a dictionary with messages (containing LangChain message objects) and data (the updated state).

How does the system handle agent execution order?

The order field in your ANALYST_CONFIG entry controls the default visual ordering in the React Flow editor and the sequence in which the graph builder adds start-node edges. However, the actual execution flow depends on the connections you draw in the front-end flow editor. You can wire your analyst to run in parallel with others or in sequence by connecting nodes accordingly.

Can I connect custom analysts to existing portfolio managers?

Yes. Once registered, your custom analyst appears as a draggable node in the React Flow editor alongside built-in analysts like Warren Buffett or Valuation. You can connect your analyst node to any existing portfolio manager node (such as the default portfolio manager) using the visual editor. The portfolio manager will automatically read signals from your agent's agent_id key in the shared analyst_signals state dictionary.

Do I need to restart the backend after adding a new analyst?

Yes, you must restart the backend service after modifying ANALYST_CONFIG in src/utils/analysts.py or adding new agent files. The graph builder in app/backend/services/graph.py reads the configuration dictionary during initialization to construct the LangGraph workflow. Once restarted, the new analyst appears in the flow editor immediately without requiring changes to the database or additional registration steps.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →