agents/investor-personas/ — Famous Investor AI Personas
13 AI agents each embodying a legendary investor's philosophy
From Warren Buffett's value investing to Nassim Taleb's tail-risk analysis, each agent analyzes stocks through its persona's unique philosophical lens. All follow the same interface but with radically different system prompts and analysis priorities.
📚 What is this & why does it exist?
These 13 agents are the creative heart of the system. Each encodes decades of a real investor's published philosophy, books, and interview quotes into an LLM system prompt. The agent then uses this persona to interpret financial data — the same P/E ratio that excites a growth investor like Cathie Wood might horrify a value investor like Ben Graham. This diversity of perspective is the core insight: no single philosophy works in all markets.
💡 Real-world analogy
Imagine a book club where each member reads the same novel but interprets it through their professional lens. A psychologist focuses on character motivations (Buffett analyzes Portfolio Management" href="../management/index.html">management quality), a statistician tracks plot patterns (the technical analyst tracks price patterns), and a critic evaluates literary technique (Graham evaluates balance sheet quality). Same data, radically different interpretations — and the discussion between them produces richer understanding than any single perspective.
🤔 Why Is It Designed This Way?
The persona-agent pattern encodes domain expertise as natural-language system prompts rather than hard-coded rules. Each agent follows an identical structural contract — (state: AgentState) → {signal, confidence, reasoning} — but swaps in a different philosophical framework via its prompt. This design was chosen over a single agent with a "style" parameter because each investor's logic requires different sub-analyses: Buffett needs moat analysis while Taleb needs tail-risk metrics. Separating agents also allows LangGraph to run them in parallel across the fan-out phase, maximizing throughput. The trade-off is code duplication across agents, but the domain separation makes each agent independently testable and modifiable.
| Design Choice | What Was Chosen | What Was Rejected | Why |
|---|---|---|---|
| Agent granularity | One file per persona | Single agent with style config | Each persona needs unique sub-analyses (moat vs. tail-risk vs. disruption) that cannot be parameterized cleanly |
| Signal format | Pydantic model with Literal types | Free-form string output | Structured output ensures downstream consumers (risk manager, portfolio manager) can reliably parse signals |
| LLM integration | Prompt-based persona + structured output | Fine-tuned models per investor | System prompts are cheaper to iterate, require no training data, and can be updated without retraining |
| Data fetching | Agent fetches its own data | Shared data layer pre-fetches all | Each persona needs different data (Burry wants insider trades; Taleb wants price volatility) so pre-fetching everything wastes API calls |
Contents
Architecture
All 13 investor persona agents follow the same four-stage pipeline pattern. The LangGraph orchestrator fans out to all agents in parallel, each independently fetching data, building analysis, calling the LLM, and emitting a structured signal.
flowchart TD
subgraph FanOut["LangGraph Fan-Out (parallel)"]
WB["warren_buffett_agent"]
BG["ben_graham_agent"]
CW["cathie_wood_agent"]
MB["michael_burry_agent"]
NT["nassim_taleb_agent"]
CM["charlie_munger_agent"]
BA["bill_ackman_agent"]
PL["peter_lynch_agent"]
PF["phil_fisher_agent"]
SD["stanley_druckenmiller_agent"]
AD["aswath_damodaran_agent"]
RJ["rakesh_jhunjhunwala_agent"]
MP["mohnish_pabrai_agent"]
end
State["AgentState with tickers"] --> FanOut
FanOut --> Signals["analyst_signals dict"]
Signals --> RM["Risk Manager"]
Per-Agent Internal Pipeline
Each persona agent internally follows this four-step pattern, regardless of which investor it embodies:
flowchart LR A["1. Fetch Data"] --> B["2. Run Sub-Analyses"] B --> C["3. Build LLM Prompt"] C --> D["4. Parse Signal Output"] A -.- A1["get_financial_metrics\nget_market_cap\nsearch_line_items"] B -.- B1["analyze_fundamentals\nanalyze_moat\nanalyze_consistency\netc."] C -.- C1["System prompt encodes\ninvestor philosophy"] D -.- D1["Pydantic model:\nsignal + confidence\n+ reasoning"]
Key Classes & Functions
| Class / Function | File | Investing Style & Focus |
|---|---|---|
warren_buffett_agent | warren_buffett.py | Value investing: moat analysis, management quality, intrinsic value vs. market price, margin of safety |
ben_graham_agent | ben_graham.py | Classic value investing: earnings stability, financial strength, Graham Number, net-net valuation |
cathie_wood_agent | cathie_wood.py | Disruptive innovation: breakthrough tech, rapid adoption curves, massive TAM, R&D intensity |
michael_burry_agent | michael_burry.py | Deep-value contrarian: insider trades, news sentiment, free cash flow, short opportunities |
nassim_taleb_agent | nassim_taleb.py | Antifragility and tail risk: price volatility, barbell strategy, convexity, fat-tail distributions |
charlie_munger_agent | charlie_munger.py | Rational thinking: quality businesses, competitive advantages, mental models, long-term compounding |
bill_ackman_agent | bill_ackman.py | Activist investing: management influence, value unlock, strategic activism, contrarian positions |
peter_lynch_agent | peter_lynch.py | Growth at reasonable price: PEG ratio, "buy what you know", understandable businesses, 10-baggers |
phil_fisher_agent | phil_fisher.py | Scuttlebutt research: strong management, innovative products, long-term growth, qualitative analysis |
stanley_druckenmiller_agent | stanley_druckenmiller.py | Macro investing: macroeconomic trends, currency/commodity bets, top-down analysis, large positions |
aswath_damodaran_agent | aswath_damodaran.py | Valuation dean: intrinsic value modeling, rigorous DCF, financial metrics, academic rigor |
rakesh_jhunjhunwala_agent | rakesh_jhunjhunwala.py | Emerging markets: macroeconomic insights, high-growth sectors, domestic opportunities, the Big Bull |
mohnish_pabrai_agent | mohnish_pabrai.py | Dhandho investing: low risk high uncertainty, copycat strategy, margin of safety, concentrated bets |
growth_analyst_agent | growth_agent.py | Growth analysis: revenue acceleration, earnings momentum, valuation multiples, growth trajectory |
Common Signal Contract
Every persona agent, regardless of which investor it embodies, must produce output conforming to this contract. The signal is always one of three values, confidence is always 0-100, and reasoning is free-form text explaining the investment thesis. This uniformity is what allows the risk manager and portfolio manager to treat all 13 agents identically.
class InvestorSignal(BaseModel):
signal: Literal["bullish", "bearish", "neutral"]
confidence: int = Field(description="Confidence level 0-100")
reasoning: str = Field(description="Investment thesis reasoning")
Sub-Analysis Scoring Pattern
Each persona agent runs multiple domain-specific sub-analyses. While the exact analyses differ (Buffett runs 7, Graham runs 3, Taleb runs 4+), they all follow the same scoring pattern: each sub-analysis returns a dict with a score key and domain-specific details. Scores are aggregated, normalized against max_possible_score, and then passed to the LLM for final interpretation through the persona's philosophical lens.
Data Structures
| Type | Name | Description |
|---|---|---|
| Pydantic | WarrenBuffettSignal | Output schema: signal (bullish/bearish/neutral), confidence (0-100 int), reasoning (str) |
| Pydantic | BenGrahamSignal | Output schema: signal (Literal), confidence (float), reasoning (str) |
| Pydantic | CathieWoodSignal | Output schema: same three fields, tailored for disruptive innovation analysis |
| Pydantic | MichaelBurrySignal | Output schema with confidence as float 0-100 for deep-value contrarian signals |
| Pydantic | NassimTalebSignal | Output schema: confidence as int Field(0-100) for tail-risk analysis |
| TypedDict | AgentState | Shared LangGraph state containing data, metadata, messages, and analyst_signals |
| dict | analysis_data | Per-ticker dict aggregating sub-analysis scores (fundamental, moat, consistency, etc.) |
| dict | ANALYST_CONFIG | Registry mapping agent keys to display names, descriptions, investing styles, and agent functions |
Interactions
| Direction | Partner | Interface | Description |
|---|---|---|---|
| → | Financial API | get_financial_metrics() | Fetches TTM/annual financial metrics for each ticker via the Financial Datasets API |
| → | Financial API | search_line_items() | Retrieves specific accounting line items (revenue, FCF, net income, etc.) with period/limit controls |
| → | Financial API | get_market_cap() | Gets current market capitalization for margin-of-safety calculations |
| → | LLM Service | call_llm() | Sends structured prompt to LLM with persona system prompt; returns parsed Pydantic signal |
| → | News/Insider API | get_insider_trades(), get_company_news() | Used by Burry and Taleb agents for contrarian and sentiment signals |
| → | Price API | get_prices(), prices_to_df() | Used by Taleb agent for volatility and tail-risk analysis on price data |
| ← | LangGraph State | state["data"]["analyst_signals"] | Each agent writes its signal dict to the shared state for downstream consumption |
| ← | Progress Tracker | progress.update_status() | Reports step-by-step progress to the UI for real-time status updates |
Configuration
| Knob / Parameter | Default | Description |
|---|---|---|
period | "ttm" or "annual" | Financial data period; Buffett uses TTM, Graham uses annual for stability analysis |
limit | 5-10 | Number of historical periods to fetch; more periods enable trend analysis |
agent_id | "{name}_agent" | Unique identifier for progress tracking and signal storage in state |
show_reasoning | metadata flag | When true, calls show_agent_reasoning() to display analysis to the user |
FINANCIAL_DATASETS_API_KEY | from state | API key for the Financial Datasets service, extracted from agent state |
Directory Map
Source tree for investor-personas
src/agents/
├── warren_buffett.py ← The Oracle of Omaha: moat, management, intrinsic value
├── ben_graham.py ← Father of Value Investing: earnings stability, Graham Number
├── cathie_wood.py ← Queen of Growth: disruptive innovation, TAM analysis
├── michael_burry.py ← The Big Short: deep-value contrarian, insider trades
├── nassim_taleb.py ← Black Swan: antifragility, tail risk, convexity
├── charlie_munger.py ← Rational Thinker: quality businesses, mental models
├── bill_ackman.py ← Activist Investor: strategic activism, value unlock
├── peter_lynch.py ← 10-Bagger Hunter: PEG ratio, buy what you know
├── phil_fisher.py ← Scuttlebutt Investor: management quality, innovation
├── stanley_druckenmiller.py ← Macro Investor: macro trends, large directional bets
├── aswath_damodaran.py ← Dean of Valuation: rigorous DCF, intrinsic value
├── rakesh_jhunjhunwala.py ← Big Bull of India: emerging markets, growth sectors
├── mohnish_pabrai.py ← Dhandho Investor: low risk high uncertainty, concentrated
└── growth_agent.py ← Growth Analyst: revenue/earnings acceleration
📑 Annotated Code Walkthrough
The most important code in this sub-module: Warren Buffett's core agent function, which exemplifies the pattern all 13 persona agents follow.
warren_buffett_agent — Core Analysis Pipeline
def warren_buffett_agent(state: AgentState, agent_id: str = "warren_buffett_agent"):
"""Analyzes stocks using Buffett's principles and LLM reasoning."""
data = state["data"]
end_date = data["end_date"]
tickers = data["tickers"]
api_key = get_api_key_from_state(state, "FINANCIAL_DATASETS_API_KEY")
analysis_data = {}
buffett_analysis = {}
for ticker in tickers:
# Step 1: Fetch raw financial data
progress.update_status(agent_id, ticker, "Fetching financial metrics")
metrics = get_financial_metrics(ticker, end_date, period="ttm", limit=10, api_key=api_key)
# Step 2: Run Buffett-specific sub-analyses
fundamental_analysis = analyze_fundamentals(metrics)
consistency_analysis = analyze_consistency(financial_line_items)
moat_analysis = analyze_moat(metrics)
pricing_power_analysis = analyze_pricing_power(financial_line_items, metrics)
book_value_analysis = analyze_book_value_growth(financial_line_items)
mgmt_analysis = analyze_management_quality(financial_line_items)
intrinsic_value_analysis = calculate_intrinsic_value(financial_line_items)
# Step 3: Aggregate sub-analysis scores
total_score = (
fundamental_analysis["score"] +
consistency_analysis["score"] +
moat_analysis["score"] +
mgmt_analysis["score"] +
pricing_power_analysis["score"] +
book_value_analysis["score"]
)
# Step 4: Calculate margin of safety (Buffett's key metric)
margin_of_safety = None
intrinsic_value = intrinsic_value_analysis["intrinsic_value"]
if intrinsic_value and market_cap:
margin_of_safety = (intrinsic_value - market_cap) / market_cap
# Step 5: Send to LLM with Buffett persona prompt
buffett_output = generate_buffett_output(
ticker=ticker, analysis_data=analysis_data[ticker],
state=state, agent_id=agent_id,
)
# Step 6: Store in standard signal format
buffett_analysis[ticker] = {
"signal": buffett_output.signal,
"confidence": buffett_output.confidence,
"reasoning": buffett_output.reasoning,
}
# Write signals to shared state
state["data"]["analyst_signals"][agent_id] = buffett_analysis
Line-by-line:
- Line 1-2: Standard signature — every persona agent takes
AgentStateand a defaultagent_id. The ID is used as the key inanalyst_signalsso downstream consumers know which persona produced which signal. - Line 6: API key extraction from state allows different API keys per run (multi-tenant), rather than relying on environment variables.
- Lines 12-13: Buffett uses TTM (trailing twelve months) with 10 periods — he cares about recent performance trends, not just the latest quarter.
- Lines 16-22: The six sub-analyses are Buffett-specific. Other agents have entirely different analyses (Taleb has
analyze_tail_risk, Cathie Wood hasanalyze_disruptive_potential). This is why each persona needs its own file. - Lines 30-32: Margin of safety — Buffett's most famous concept. He only buys when intrinsic value exceeds market price by a significant margin. This raw metric is passed to the LLM for interpretation.
- Lines 35-38: The LLM call is where the persona really lives.
generate_buffett_outputwraps the analysis data in a system prompt that channels Buffett's voice and priorities. - Lines 41-44: Output is normalized to the standard
{signal, confidence, reasoning}format that the risk manager and portfolio manager expect from all agents.
⚠️ Edge Cases & Boundaries
| Edge Case | What Happens | Where in Code | Risk Level |
|---|---|---|---|
| Missing financial metrics | Sub-analysis functions return zero scores; LLM receives incomplete data and may produce low-confidence signals | All agents: metrics fetch check | 🔴 High |
| API key not in state | get_api_key_from_state() raises or returns None, causing all data fetches to fail for the agent | Agent entry: line 6 | 🔴 High |
| Market cap is None | Margin of safety is set to None and intrinsic value comparison is skipped | warren_buffett.py:103-106 | 🟡 Medium |
| LLM returns malformed output | Pydantic validation fails; the agent does not produce a signal for that ticker | generate_*_output functions | 🟡 Medium |
| No insider trades available | Burry and Taleb agents get empty lists; sentiment analysis produces neutral signals | michael_burry.py:70, nassim_taleb.py:76 | 🟢 Low |
| Empty tickers list | The for-loop body never executes; agent returns empty analysis dict | All agents: for ticker in tickers | 🟢 Low |
Code Examples
Example: Ben Graham's Score-Based Signal Mapping
Graham's agent demonstrates the simpler scoring approach where scores directly map to signals without an LLM call for the initial assessment, though the LLM refines the final output:
# Aggregate scoring from three sub-analyses
total_score = (
earnings_analysis["score"] +
strength_analysis["score"] +
valuation_analysis["score"]
)
max_possible_score = 15 # 5 per analysis
# Direct score-to-signal mapping (deterministic)
if total_score >= 0.7 * max_possible_score: # >= 10.5
signal = "bullish"
elif total_score <= 0.3 * max_possible_score: # <= 4.5
signal = "bearish"
else:
signal = "neutral"
Example: Cathie Wood's Disruptive Innovation Focus
Note how Cathie Wood's agent fetches R&D and capex data that most other agents don't need — her analysis is uniquely focused on innovation spending:
financial_line_items = search_line_items(
ticker,
[
"revenue", "gross_margin", "operating_margin",
"research_and_development", # Unique to Cathie Wood
"capital_expenditure", # Innovation investment proxy
"operating_expense", # R&D as % of opex
],
end_date, period="annual", limit=5, api_key=api_key,
)
Data Flow
How a single ticker flows through one persona agent from state input to signal output:
sequenceDiagram
participant S as AgentState
participant A as Persona Agent
participant API as Financial API
participant LLM as LLM Service
participant Out as analyst_signals
S->>A: tickers, end_date, api_key
loop For each ticker
A->>API: get_financial_metrics(ticker)
API-->>A: metrics list
A->>API: search_line_items(ticker)
API-->>A: line items
A->>API: get_market_cap(ticker)
API-->>A: market cap
A->>A: Run sub-analyses (moat, fundamentals, etc.)
A->>LLM: Persona prompt + analysis_data
LLM-->>A: WarrenBuffettSignal(signal, confidence, reasoning)
end
A->>Out: analyst_signals[agent_id] = analysis