traders.analyze
Analyze a Polymarket trader by wallet address to evaluate their quality before adding to your watchlist. Returns profile stats, active positions, win rate, volume, PnL, and recent trade activity.
Instructions
Analyze a Polymarket trader by wallet address. Returns profile stats, active positions, win rate, volume, PnL, and recent trade activity. Use before adding a trader to your watchlist to assess their quality.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| address | Yes | Trader's Ethereum wallet address (0x...) to analyze |
Implementation Reference
- src/index.ts:218-223 (registration)Registration of the 'traders.analyze' tool with the MCP server. Uses analyzeTraderSchema for input validation and delegates to handleAnalyzeTrader.
server.tool( "traders.analyze", "Analyze a Polymarket trader by wallet address. Returns profile stats, active positions, win rate, volume, PnL, and recent trade activity. Use before adding a trader to your watchlist to assess their quality.", analyzeTraderSchema.shape, safe("traders.analyze", async (input) => ({ content: [{ type: "text" as const, text: await handleAnalyzeTrader(analyzeTraderSchema.parse(input)) }] })) ); - src/tools/analyze-trader.ts:5-7 (schema)Zod schema for 'traders.analyze' input: expects 'address' as a valid Ethereum address (0x + 40 hex chars).
export const analyzeTraderSchema = z.object({ address: z.string().regex(/^0x[a-fA-F0-9]{40}$/, "Must be a valid Ethereum address (0x + 40 hex chars)").describe("Trader's Ethereum wallet address (0x...) to analyze"), }); - src/tools/analyze-trader.ts:9-31 (handler)Handler function for 'traders.analyze'. Checks license, calls the analyzeTrader service, and formats the output as a markdown table with active positions, win rate, avg position size, and (pro-only) recent trades.
export async function handleAnalyzeTrader(input: z.infer<typeof analyzeTraderSchema>): Promise<string> { const isPro = await checkLicense(); const profile = await analyzeTrader(input.address, isPro); let output = `## Trader Analysis: ${input.address.slice(0, 6)}...${input.address.slice(-4)}\n\n`; output += `| Metric | Value |\n|--------|-------|\n`; output += `| Active Positions | ${profile.activePositions} |\n`; output += `| Win Rate | ${profile.winRate.toFixed(1)}% |\n`; output += `| Avg Position Size | $${profile.avgPositionSize.toFixed(2)} |\n`; if (isPro && profile.recentTrades.length > 0) { output += `\n### Recent Trades\n\n`; output += `| Time | Market | Side | Size | Price |\n|------|--------|------|------|-------|\n`; for (const t of profile.recentTrades) { const time = t.timestamp?.split("T")[1]?.slice(0, 5) ?? "-"; output += `| ${time} | ${t.title.slice(0, 30)} | ${t.side} | ${t.size.toFixed(2)} | $${t.price.toFixed(2)} |\n`; } } else if (!isPro) { output += `\n_Upgrade to Pro for detailed trade history._\n`; } return output; } - Core service function 'analyzeTrader': fetches activity and positions from Polymarket data API, computes win rate from buys/sells, estimates P&L, and returns a TraderProfile.
export async function analyzeTrader(address: string, detailed: boolean): Promise<TraderProfile> { const [activities, positions] = await Promise.all([ fetchTraderActivity(address), fetchTraderPositions(address), ]); const trades = activities.map((a: any) => ({ title: a.title ?? "", side: a.side ?? "", size: parseFloat(a.size ?? "0"), price: parseFloat(a.price ?? "0"), timestamp: a.timestamp ?? "", outcome: a.outcome ?? "", })); // Win rate: SELL trades where exit price > avg entry price indicate profit const sells = trades.filter((t: RecentTrade) => t.side === "SELL"); const buys = trades.filter((t: RecentTrade) => t.side === "BUY"); // Build average entry price per market title const entryPrices = new Map<string, { sum: number; count: number }>(); for (const b of buys) { const existing = entryPrices.get(b.title); if (existing) { existing.sum += b.price; existing.count++; } else { entryPrices.set(b.title, { sum: b.price, count: 1 }); } } const avgEntry = new Map<string, number>(); for (const [title, { sum, count }] of entryPrices) { avgEntry.set(title, sum / count); } const wins = sells.filter((s: RecentTrade) => { const entry = avgEntry.get(s.title); return entry !== undefined && s.price > entry; }).length; const total = sells.length; const winRate = total > 0 ? (wins / total) * 100 : 0; const avgSize = trades.length > 0 ? trades.reduce((sum: number, t: RecentTrade) => sum + t.size * t.price, 0) / trades.length : 0; // Calculate estimated P&L from closed trades (sells with matching buys) const totalPnl = sells.reduce((sum: number, s: RecentTrade) => { const entry = avgEntry.get(s.title); if (entry === undefined || entry === 0) return sum; return sum + ((s.price - entry) * s.size); }, 0); return { address, activePositions: positions.length, recentTrades: detailed ? trades.slice(0, 10) : [], totalPnl, winRate, avgPositionSize: avgSize, }; } - Helper function 'fetchTraderActivity': fetches trade activity for a wallet address from the Polymarket data API.
async function fetchTraderActivity(address: string): Promise<any[]> { try { const url = `${DATA_API_BASE}/activity?user=${address}&type=TRADE&limit=50`; const res = await fetchWithRetry(url); if (!res.ok) return []; return await res.json(); } catch (err) { log("error", `Failed to fetch activity for ${address}: ${err}`); return []; } }