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
| Name | Required | Description | Default |
|---|---|---|---|
| ticker | Yes | Stock ticker symbol (e.g. NVDA, AAPL, MSFT) |
Implementation Reference
- src/tools/insider-signal.ts:40-153 (handler)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(), }; } - src/tools/insider-signal.ts:155-173 (registration)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; - src/tools/insider-signal.ts:6-13 (schema)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)"), });