Skip to main content
Glama

Bear vs Bull

bear_vs_bull

Analyze stock investments by generating structured bull and bear cases with specific data, delivering a net verdict and key investor questions for informed decisions.

Instructions

Generate a structured bull vs. bear case for any stock. Steelmans both sides with specific data, then delivers a net verdict and the key question investors need to answer before buying.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
tickerYesStock ticker symbol (e.g. NVDA, AAPL, MSFT)

Implementation Reference

  • The handler function for the bear-vs-bull tool, which fetches market data and uses an LLM to generate a bull/bear analysis.
    async function handler(input: Input) {
      const { ticker } = input;
    
      if (!config.anthropicApiKey) throw new Error("ANTHROPIC_API_KEY is not configured");
      if (!config.polygonApiKey) throw new Error("POLYGON_API_KEY is not configured");
      if (!config.finnhubApiKey) throw new Error("FINNHUB_API_KEY is not configured");
      if (!config.fmpApiKey) throw new Error("FMP_API_KEY is not configured");
    
      const [overview, prevClose, incomeStatements, keyMetrics, finnhubMetrics, recommendations, insiders] =
        await Promise.all([
          fetchPolygonOverview(ticker),
          fetchPolygonPrevClose(ticker),
          fetchFMPIncomeStatement(ticker),
          fetchFMPKeyMetrics(ticker),
          fetchFinnhubMetrics(ticker),
          fetchFinnhubRecommendations(ticker),
          fetchFinnhubInsiders(ticker),
        ]);
    
      const hasData = Object.keys(overview).length > 0 || (incomeStatements as any[]).length > 0;
      if (!hasData) {
        throw new Error(`No data found for "${ticker}". Please verify the symbol.`);
      }
    
      const ov = overview as any;
      const pc = prevClose as any;
      const km = keyMetrics as any;
      const fh = finnhubMetrics as any;
      const rec = (recommendations as any[])[0];
    
      const incomeRows = (incomeStatements as any[]).map((s: any) => {
        const rev = s.revenue ? `$${(s.revenue / 1e9).toFixed(2)}B` : "N/A";
        const margin = s.netIncomeRatio != null ? `${(s.netIncomeRatio * 100).toFixed(1)}% net margin` : "N/A";
        const eps = s.eps != null ? `EPS $${s.eps.toFixed(2)}` : "";
        return `  ${s.calendarYear ?? s.date?.substring(0, 4)}: Revenue ${rev} | ${margin}${eps ? ` | ${eps}` : ""}`;
      });
    
      const insiderPurchases = (insiders as any[]).filter((t: any) => t.transactionCode === "P").length;
      const insiderSales = (insiders as any[]).filter((t: any) => t.transactionCode === "S").length;
    
      const dataContext = [
        `Company: ${ov.name || ticker} (${ticker})`,
        ov.sic_description ? `Sector: ${ov.sic_description}` : "",
        ov.market_cap ? `Market Cap: $${(ov.market_cap / 1e9).toFixed(1)}B` : "",
        pc.c ? `Current Price: $${pc.c}` : "",
        ov.description ? `\nBusiness: ${ov.description.substring(0, 400)}` : "",
        incomeRows.length > 0 ? `\nFinancials:\n${incomeRows.join("\n")}` : "",
        fh.revenueGrowth3Y != null ? `3Y Revenue CAGR: ${Number(fh.revenueGrowth3Y).toFixed(1)}%` : "",
        `\nValuation:`,
        (km.peRatioTTM ?? fh.peNormalizedAnnual) != null ? `  P/E (TTM): ${Number(km.peRatioTTM ?? fh.peNormalizedAnnual).toFixed(1)}x` : "",
        (km.priceToSalesRatioTTM ?? fh.psTTM) != null ? `  P/S: ${Number(km.priceToSalesRatioTTM ?? fh.psTTM).toFixed(1)}x` : "",
        km.freeCashFlowYieldTTM != null ? `  FCF Yield: ${(Number(km.freeCashFlowYieldTTM) * 100).toFixed(1)}%` : (fh.pfcfShareTTM > 0 ? `  FCF Yield: ${(100 / Number(fh.pfcfShareTTM)).toFixed(1)}%` : ""),
        (km.debtToEquityTTM ?? fh["totalDebt/totalEquityAnnual"]) != null ? `  Debt/Equity: ${Number(km.debtToEquityTTM ?? fh["totalDebt/totalEquityAnnual"]).toFixed(2)}` : "",
        rec ? `\nAnalysts: ${rec.buy} buy / ${rec.hold} hold / ${rec.sell} sell` : "",
        `\nInsider activity (recent): ${insiderPurchases} open-market purchases, ${insiderSales} open-market sales`,
      ].filter(Boolean).join("\n");
    
      const client = new Anthropic({ apiKey: config.anthropicApiKey });
    
      const systemPrompt =
        "You are a balanced, rigorous stock analyst in the style of The Motley Fool. " +
        "Your job is to steelman BOTH the bull and bear cases with equal intellectual honesty. " +
        "The bull case should be genuinely optimistic with specific data. " +
        "The bear case should be genuinely challenging — not strawman risks. " +
        "The net verdict should be your honest synthesis of both sides. " +
        "Write clearly for a retail investor. Always respond with valid JSON.";
    
      const userPrompt =
        `Build a rigorous bull vs. bear case for ${ticker}.\n\n` +
        dataContext +
        `\n\nReturn a JSON object with this exact structure:
    {
      "companyName": "full company name",
      "bullCase": [
        { "argument": "title of the bull point", "detail": "2-3 sentences explaining it with specific data" },
        { "argument": "title", "detail": "..." },
        { "argument": "title", "detail": "..." }
      ],
      "bearCase": [
        { "argument": "title of the bear point", "detail": "2-3 sentences explaining the genuine risk" },
        { "argument": "title", "detail": "..." },
        { "argument": "title", "detail": "..." }
      ],
      "verdict": "bull_wins" | "slight_bull" | "too_close" | "slight_bear" | "bear_wins",
      "verdictRationale": "2-3 sentences explaining which side is more compelling and why. Be direct.",
      "keyDebate": "the single most important question investors need to answer about this stock",
      "forInvestorsWho": "1 sentence describing what type of investor this stock suits (or doesn't suit)"
    }`;
    
      const message = await client.messages.create({
        model: "claude-haiku-4-5-20251001",
        max_tokens: 1500,
        messages: [{ role: "user", content: userPrompt }],
        system: systemPrompt,
      });
    
      const rawText = message.content[0].type === "text" ? message.content[0].text : "";
      const jsonText = rawText.replace(/^```(?:json)?\n?/m, "").replace(/\n?```$/m, "").trim();
    
      let parsed: Record<string, unknown>;
      try {
        parsed = JSON.parse(jsonText);
      } catch {
        throw new Error("Failed to parse structured response from LLM");
      }
    
      return {
        ticker,
        ...parsed,
        generatedAt: new Date().toISOString(),
      };
    }
  • Schema definition for the input to the bear-vs-bull tool.
    const inputSchema = z.object({
      ticker: z
        .string()
        .min(1)
        .max(10)
        .transform((v) => v.toUpperCase().trim())
        .describe("Stock ticker symbol (e.g. NVDA, AAPL, MSFT)"),
    });
  • Definition and registration of the bear-vs-bull tool.
    const bearVsBullTool: ToolDefinition<Input> = {
      name: "bear-vs-bull",
      description:
        "Generate a structured bull vs. bear case for any stock. Steelmans both sides equally — 3 bull arguments " +
        "and 3 bear arguments with specific data, then delivers a net verdict and the key question investors need " +
        "to answer. Great for stress-testing a thesis or getting a balanced view before investing. Powered by Claude.",
      version: "1.0.0",
      inputSchema,
      handler,
      metadata: {
        tags: ["stocks", "investing", "finance", "analysis", "llm"],
        pricing: "$0.05 per call",
        pricingMicros: 50_000,
        exampleInput: { ticker: "NVDA" },
      },
    };
    
    registerTool(bearVsBullTool);
    export default bearVsBullTool;
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden. It successfully discloses the methodology ('Steelmans both sides with specific data') and output format ('net verdict', 'key question'), but lacks operational details like data freshness, sources, or real-time vs. historical limitations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, efficiently structured sentence that front-loads the core action ('Generate a structured bull vs. bear case') and follows with methodology and output details. Zero redundancy—every clause earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a single-parameter tool without annotations or output schema, the description adequately compensates by explaining the return structure ('structured case', 'net verdict', 'key question'). It could be improved with data source transparency, but sufficiently covers the tool's behavior and output format.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Input schema has 100% description coverage for the single 'ticker' parameter. The description mentions 'for any stock' which maps to the parameter, but adds no additional semantic detail beyond the schema's own definition. Baseline 3 is appropriate given schema completeness.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses specific verbs ('Generate', 'Steelmans', 'delivers') and clearly identifies the resource (bull vs. bear case for stocks). It effectively distinguishes from siblings like 'stock_thesis' and 'earnings_analysis' by emphasizing the balanced, dialectical approach and final verdict structure.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage context ('key question investors need to answer before buying') suggesting it's for pre-investment analysis, but lacks explicit when-to-use guidance compared to siblings like 'stock_thesis' or 'valuation_snapshot'. No explicit exclusions or alternatives are named.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/marras0914/agent-toolbelt'

If you have feedback or need assistance with the MCP directory API, please join our Discord server