Skip to main content
Glama

Insider Signal

insider_signal

Analyzes insider trading activity for stocks to classify purchase types, identify cluster buying patterns, and provide clear signal strength assessments with plain-English explanations.

Instructions

Interpret insider trading activity for any stock. Classifies open-market purchases vs. routine sales/awards, identifies cluster buying, and explains whether the activity is a meaningful signal. Returns signal strength (strong_buy → strong_sell) and a plain-English verdict.

Input Schema

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

Implementation Reference

  • The handler function implements the core logic for the 'insider-signal' tool, fetching transaction and sentiment data from Finnhub, passing it to Claude for analysis, and returning a structured JSON response.
    async function handler(input: Input) {
      const { ticker } = input;
    
      if (!config.anthropicApiKey) throw new Error("ANTHROPIC_API_KEY is not configured");
      if (!config.finnhubApiKey) throw new Error("FINNHUB_API_KEY is not configured");
    
      const [transactions, sentiment] = await Promise.all([
        fetchInsiderTransactions(ticker),
        fetchInsiderSentiment(ticker),
      ]);
    
      if ((transactions as any[]).length === 0) {
        throw new Error(`No insider transaction data found for "${ticker}". Please verify the symbol.`);
      }
    
      // Classify transactions
      const recent = (transactions as any[]).slice(0, 20);
      const purchases = recent.filter((t: any) => t.transactionCode === "P");
      const sales = recent.filter((t: any) => t.transactionCode === "S");
      const grantAwards = recent.filter((t: any) => ["A", "M", "F"].includes(t.transactionCode));
    
      const totalPurchaseShares = purchases.reduce((sum: number, t: any) => sum + (t.change || 0), 0);
      const totalSaleShares = Math.abs(sales.reduce((sum: number, t: any) => sum + (t.change || 0), 0));
    
      // Format notable trades for the prompt
      const tradeRows = recent.slice(0, 15).map((t: any) => {
        const direction = t.transactionCode === "P" ? "PURCHASE"
          : t.transactionCode === "S" ? "SALE"
          : t.transactionCode === "A" ? "AWARD"
          : t.transactionCode === "M" ? "OPTION_EXERCISE"
          : t.transactionCode === "F" ? "TAX_WITHHOLDING"
          : t.transactionCode;
        const shares = t.change != null ? `${t.change > 0 ? "+" : ""}${t.change.toLocaleString()} shares` : "";
        const price = t.transactionPrice ? ` @ $${t.transactionPrice.toFixed(2)}` : "";
        const value = t.transactionPrice && t.change
          ? ` ($${Math.abs(Math.round(t.transactionPrice * t.change / 1000))}k)`
          : "";
        return `  ${t.transactionDate}: ${t.name} (${t.position || "insider"}) — ${direction} ${shares}${price}${value}`;
      });
    
      // Sentiment data
      const sentimentRows = (sentiment as any[]).map((s: any) =>
        `  ${s.year}-${String(s.month).padStart(2, "0")}: MSPR ${s.mspr?.toFixed(2) ?? "N/A"} | Change ${s.change?.toLocaleString() ?? "N/A"} shares`
      );
    
      const dataContext = [
        `Ticker: ${ticker}`,
        `Recent transactions analyzed: ${recent.length}`,
        `Open market purchases: ${purchases.length} trades (+${totalPurchaseShares.toLocaleString()} shares total)`,
        `Open market sales: ${sales.length} trades (-${totalSaleShares.toLocaleString()} shares total)`,
        `Awards/option exercises/tax withholding: ${grantAwards.length} transactions (typically routine)`,
        "",
        "Transaction detail (most recent first):",
        ...tradeRows,
        sentimentRows.length > 0 ? "\nInsider Sentiment (monthly MSPR score, +1 = max bullish, -1 = max bearish):" : "",
        ...sentimentRows,
      ].filter(Boolean).join("\n");
    
      const client = new Anthropic({ apiKey: config.anthropicApiKey });
    
      const systemPrompt =
        "You are a professional stock analyst specializing in insider trading signal interpretation. " +
        "Write clearly for a retail investor who wants to know: is this insider activity meaningful? " +
        "Key distinctions: open-market purchases (strong signal), open-market sales (weak/mixed signal — could be diversification), " +
        "awards and option exercises (routine, not meaningful), cluster buying by multiple insiders (strong signal). " +
        "Always respond with valid JSON matching the exact schema. Base analysis strictly on provided data.";
    
      const userPrompt =
        `Interpret the insider trading activity for ${ticker}. Is it a meaningful signal?\n\n` +
        dataContext +
        `\n\nReturn a JSON object with this exact structure:
    {
      "signal": "strong_buy" | "buy" | "neutral" | "sell" | "strong_sell",
      "confidence": "high" | "medium" | "low",
      "oneLiner": "one sentence capturing the key signal from insider activity",
      "interpretation": "2-3 sentences interpreting the overall pattern. Distinguish meaningful open-market purchases from routine sales/awards.",
      "notableTrades": [
        { "who": "name and title", "action": "what they did", "significance": "why it matters or why it doesn't" }
      ],
      "buyingPressure": "description of purchasing activity",
      "sellingPressure": "description of selling activity and whether it's likely routine or conviction-based",
      "verdict": "2 sentences — net bottom line for a long-term investor: should this insider activity change your view of the stock?"
    }`;
    
      const message = await client.messages.create({
        model: "claude-haiku-4-5-20251001",
        max_tokens: 1024,
        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,
        rawData: {
          transactionsAnalyzed: recent.length,
          openMarketPurchases: purchases.length,
          openMarketSales: sales.length,
          routineTransactions: grantAwards.length,
          netSharesPurchased: totalPurchaseShares - totalSaleShares,
        },
        generatedAt: new Date().toISOString(),
      };
    }
  • Registration of the 'insider-signal' tool using the ToolDefinition structure and the registerTool helper.
    const insiderSignalTool: ToolDefinition<Input> = {
      name: "insider-signal",
      description:
        "Interpret insider trading activity for any stock. Classifies open-market purchases vs. routine sales/awards, " +
        "identifies cluster buying, and explains whether the activity is a meaningful buy or sell signal. " +
        "Returns signal strength (strong_buy → strong_sell), confidence, and a plain-English verdict. Powered by Claude.",
      version: "1.0.0",
      inputSchema,
      handler,
      metadata: {
        tags: ["stocks", "investing", "finance", "insider-trading", "llm"],
        pricing: "$0.05 per call",
        pricingMicros: 50_000,
        exampleInput: { ticker: "NVDA" },
      },
    };
    
    registerTool(insiderSignalTool);
    export default insiderSignalTool;
  • Input validation schema for the 'insider-signal' tool, requiring a ticker string.
    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)"),
    });
Behavior4/5

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

With no annotations provided, the description carries the full burden and succeeds by disclosing the classification logic (open-market vs routine), detection heuristics (cluster buying), and return format (strong_buy → strong_sell scale plus plain-English verdict). Missing only operational constraints like rate limits or data freshness.

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?

Every sentence earns its place: front-loaded purpose ('Interpret insider trading activity'), followed by processing logic ('Classifies... identifies... explains'), and concludes with return value specification. Zero redundancy in two dense sentences.

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

Completeness5/5

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

Despite lacking an output schema, the description fully compensates by detailing the return structure (signal strength scale and verdict format) and explaining the analytical methodology applied to the input, making it complete for a single-parameter analysis tool.

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?

Schema coverage is 100% with the 'ticker' parameter fully documented including examples (NVDA, AAPL, MSFT). The description adds no explicit parameter semantics, but baseline 3 is appropriate given the schema already provides complete documentation.

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?

Description uses specific verb 'Interpret' with resource 'insider trading activity' and clearly distinguishes from siblings like 'stock_thesis' or 'earnings_analysis' by emphasizing unique capabilities: classifying open-market purchases vs routine sales, identifying cluster buying, and providing signal strength ratings.

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?

While the specific focus on insider trading implies appropriate usage context, the description lacks explicit guidance on when to select this tool over siblings like 'bear_vs_bull' or 'valuation_snapshot', and does not mention prerequisites or exclusions.

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