← Raghav Gupta
§ Case study 03 / 05Shipped · open source

Equity Crew

Multi-agent equity research

An LLM that says “BUY” is worthless. An LLM that says BUY at 0.78 confidence, with a 12-month target, two named reasons and two named risks — and gets retried when it fails to produce them — is at least something you can argue with.

Stack
Python · CrewAI · OpenRouter · EXA
Agents
6 defined · 4 run in parallel
Output
Validated schema + PDF report

The problem

Every “AI stock analyst” demo is one prompt over a price feed. The output is fluent and unfalsifiable: no sources, no structure, no way to separate a well-supported answer from a confident invention. That is a test-design problem before it is a modelling problem — if the output has no schema, there is nothing to assert against.

So Equity Crew was built backwards from the assertion. Decide what a defensible recommendation must contain, make that a type, put a guardrail in front of the type, and only then work out which agents have to exist to fill it in.

How it works

Four independent crews run concurrently in a ThreadPoolExecutor — fundamentals, news, technicals, peers. Each writes a markdown artifact. A synthesis agent then reads all four, and an advisor agent turns the synthesis into a structured recommendation that has to survive a guardrail before anything downstream sees it.

At the end of every run the system reports what parallelism actually bought — phase 1 wall-clock against an estimated sequential baseline — rather than claiming a number once in a README and never measuring it again.

                 CLI  ·  main.py --stock SYRMA.NS
                          │
                 startup validation
                 env vars · symbol format · live API reachability
                          │
   ╔══════════════════════▼══════════════════════════════╗
   ║   PHASE 1 — four crews, ThreadPoolExecutor(4)       ║
   ╚══╤═══════════╤════════════╤════════════╤════════════╝
      │           │            │            │
 ┌────▼────┐ ┌────▼────┐ ┌─────▼─────┐ ┌────▼─────────┐
 │Financial│ │  News   │ │ Technical │ │    Peers     │
 │8 yfinance│ │ EXA     │ │ RSI MACD  │ │ LLM picks   │
 │tools     │ │ neural  │ │ BB SMA    │ │ 4–5 comps,  │
 │          │ │ search  │ │ volume    │ │ no hardcode │
 └────┬─────┘ └────┬────┘ └─────┬─────┘ └────┬─────────┘
      └────────────┴────────────┴────────────┘
                          │
   ╔══════════════════════▼══════════════════════════════╗
   ║   PHASE 2 — sequential                              ║
   ╚══════════════════════╤══════════════════════════════╝
                          │
              analyst   ·  no tools by design
                          │
              fin_expert ·  InvestmentRecommendation
                          │  ├─ action: BUY | HOLD | SELL
                          │  ├─ confidence: 0.0–1.0
                          │  ├─ target_price, current_price
                          │  ├─ reasons: ≥ 2
                          │  └─ risks:   ≥ 1
                          │
                 guardrail fails ──► CrewAI retries the task
                          │           with the error as feedback
                          ▼
              markdown artifacts + [SYMBOL]_Report.pdf
Page one of a generated equity research report for SYRMA.NS, showing a green BUY recommendation badge, 78% confidence, current price and 12-month target, key reasons and key risks side by side, and a key financial metrics grid.
Page 1 of 15 from a generated run — SYRMA.NS, 01 May 2026. The badge, the confidence, the target and the reasons/risks columns are the guardrailed schema, rendered.

Decisions worth defending

  1. 01

    The output schema came before the agents.

    InvestmentRecommendation is a Pydantic model: a Literal["BUY","HOLD","SELL"], a confidence bounded to 0–1, a target and current price, at least two reasons and at least one risk. A guardrail function returns (False, message) when any of that is missing, and CrewAI re-runs the task with the message as feedback. The model does not get to decide what a complete answer looks like.

  2. 02

    The synthesiser is given no tools, deliberately.

    The analyst agent has zero tools. It cannot go back and re-fetch; it has to reason from the structured phase-1 context it was handed. That keeps the synthesis step reproducible from its inputs, and it stops the agent papering over a gap in the data by quietly pulling more of it — which is exactly the behaviour that makes an agent's output impossible to review.

  3. 03

    Technical indicators written from scratch.

    RSI, MACD, Bollinger bands, the 50/200 SMA cross and volume ratio are about forty lines of vectorised pandas rather than a TA library. One dependency fewer, and every number in the report traces to arithmetic that lives in this repo and is unit-tested directly in tests/test_tools.py. A borrowed indicator you cannot assert on is a number you are trusting for no reason.

  4. 04

    Peers are chosen at runtime, not hardcoded.

    The sector analyst uses the model's own market knowledge to name four or five comparables, then pulls a compact valuation snapshot for each. No peer map to go stale, and it works for any ticker on any exchange — which matters when the same tool has to handle an NSE small-cap and a US mega-cap.

  5. 05

    Failures are told to the operator, not the log.

    401s, 429s, agent timeouts and interrupts are caught and converted into a message that names the cause and the fix, with the traceback kept to the log file. python main.py --validate is the preflight: it checks credentials and live API reachability before a run spends twenty minutes and a token budget on something that was going to fail authentication at minute one.

  6. 06

    Retry logic is fifteen lines, not a dependency.

    _with_retry wraps every yfinance call with exponential backoff — 1.5s, 3s, 6s — and every tool follows the same shape: validate input, retry, return structured JSON, or return a descriptive error string the agent can actually reason about. Agents recover from a legible error; they flail on a stack trace.

Proving it works

The agents are the interesting part and the tests are the part that makes them reviewable.