pulse_trader_closed_positions
Retrieve closed position history for any wallet, including entry/exit prices, hold duration, PnL, and leverage. Analyze trader lifecycle and timing patterns.
Instructions
Get closed position history for any wallet. Shows every position that was opened and closed — with entry/exit prices, hold duration, PnL, and leverage. Use this to analyze a trader's position lifecycle and timing patterns. Answers: 'Show me all historical positions for this trader', 'What was the PnL and duration of each position?', 'When did this whale close their massive ETH long?'
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| useToonFormat | No | Return data in compact toon format (default: true). Set to false for standard JSON. | |
| address | Yes | Ethereum wallet address (0x...) | |
| limit | No | Number of positions to return | |
| offset | No | Pagination offset | |
| coin | No | Filter by coin symbol (e.g. BTC, ETH, SOL). For builder dex: prefix:COIN (e.g. xyz:SILVER) |
Implementation Reference
- src/index.ts:852-872 (registration)Tool registration for 'pulse_trader_closed_positions' using server.registerTool() with the name 'pulse_trader_closed_positions'. The registration includes a detailed description and input schema (useToonFormat, address, limit, offset, coin), and is guarded by shouldRegister() to check API key availability.
// ══════════════════════════════════════════════════════════ // TOOL 24: Trader Closed Positions // ══════════════════════════════════════════════════════════ if (shouldRegister("pulse_trader_closed_positions")) server.registerTool( "pulse_trader_closed_positions", { description: "Get closed position history for any wallet. Shows every position that was opened and closed — with entry/exit prices, hold duration, PnL, and leverage. Use this to analyze a trader's position lifecycle and timing patterns. Answers: 'Show me all historical positions for this trader', 'What was the PnL and duration of each position?', 'When did this whale close their massive ETH long?'", inputSchema: { useToonFormat: useToonFormatSchema, address: ethAddressSchema, limit: z.number().min(1).max(200).default(50).describe("Number of positions to return"), offset: z.number().min(0).default(0).describe("Pagination offset"), coin: z.string().optional().describe("Filter by coin symbol (e.g. BTC, ETH, SOL). For builder dex: prefix:COIN (e.g. xyz:SILVER)"), }, }, async ({ useToonFormat, address, limit, offset, coin }) => { const params: Record<string, string> = { limit: String(limit), offset: String(offset) }; if (coin) params.coin = normalizeCoin(coin); return toolResult(await callAPI(useToonFormat, `/pulse/trader/${address}/closed-positions`, params)); } ); - src/index.ts:867-872 (handler)Handler function for pulse_trader_closed_positions. Accepts useToonFormat, address, limit, offset, and optional coin parameters. Builds query params and delegates to callAPI() to fetch data from the backend endpoint `/pulse/trader/${address}/closed-positions`.
async ({ useToonFormat, address, limit, offset, coin }) => { const params: Record<string, string> = { limit: String(limit), offset: String(offset) }; if (coin) params.coin = normalizeCoin(coin); return toolResult(await callAPI(useToonFormat, `/pulse/trader/${address}/closed-positions`, params)); } ); - src/index.ts:857-866 (schema)Input schema for pulse_trader_closed_positions using Zod: useToonFormat (boolean, default true), address (Ethereum address regex), limit (1-200, default 50), offset (min 0, default 0), and optional coin filter (string).
{ description: "Get closed position history for any wallet. Shows every position that was opened and closed — with entry/exit prices, hold duration, PnL, and leverage. Use this to analyze a trader's position lifecycle and timing patterns. Answers: 'Show me all historical positions for this trader', 'What was the PnL and duration of each position?', 'When did this whale close their massive ETH long?'", inputSchema: { useToonFormat: useToonFormatSchema, address: ethAddressSchema, limit: z.number().min(1).max(200).default(50).describe("Number of positions to return"), offset: z.number().min(0).default(0).describe("Pagination offset"), coin: z.string().optional().describe("Filter by coin symbol (e.g. BTC, ETH, SOL). For builder dex: prefix:COIN (e.g. xyz:SILVER)"), }, }, - src/index.ts:84-91 (helper)normalizeCoin helper used by the handler to normalize coin/symbol strings (uppercasing the coin part while preserving lowercase builder dex prefix).
function normalizeCoin(raw: string): string { const idx = raw.indexOf(':'); if (idx !== -1) { // Builder dex format — keep prefix lowercase, uppercase the coin return raw.slice(0, idx).toLowerCase() + ':' + raw.slice(idx + 1).toUpperCase(); } return raw.toUpperCase(); } - src/index.ts:94-166 (helper)callAPI helper used by the handler to make HTTP requests to the Coinversa API with timeout, retries, and error handling.
async function callAPI(useToon: boolean, path: string, params?: Record<string, string>): Promise<any> { const url = new URL(`${BASE}${path}`); if (params) { Object.entries(params).forEach(([key, value]) => { if (value !== undefined && value !== "") { url.searchParams.set(key, value); } }); } let lastError: Error | null = null; for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) { try { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS); const headers: Record<string, string> = {}; if (API_KEY) headers["X-API-Key"] = API_KEY; const response = await fetch(url.toString(), { headers, signal: controller.signal, }); clearTimeout(timeout); if (response.status === 429) { // Rate limited — retry after delay if (attempt < MAX_RETRIES) { await new Promise((r) => setTimeout(r, RETRY_DELAY_MS * (attempt + 1))); continue; } throw new Error("Rate limit exceeded. Please wait a moment and try again."); } if (response.status === 404) { throw new Error("Not found. The requested resource does not exist — check the address or symbol."); } if (response.status === 401) { throw new Error("Invalid API key. Check your COINVERSAA_API_KEY environment variable."); } if (!response.ok) { const body = await response.json().catch(() => null); const msg = body?.error || response.statusText; throw new Error(`Request failed (${response.status}): ${msg}`); } const data = await response.json(); return useToon ? toonEncode(data) : data; } catch (err: any) { if (err.name === "AbortError") { lastError = new Error("Request timed out after 30 seconds. The server may be under heavy load — try again."); } else if (err.cause?.code === "ECONNREFUSED" || err.cause?.code === "ENOTFOUND") { lastError = new Error("Cannot connect to the Coinversa API. Check your COINVERSAA_API_URL setting and network connection."); } else { lastError = err; } // Retry on transient network errors if (attempt < MAX_RETRIES && (err.name === "AbortError" || err.cause?.code === "ECONNRESET")) { await new Promise((r) => setTimeout(r, RETRY_DELAY_MS * (attempt + 1))); continue; } throw lastError; } } throw lastError || new Error("Request failed after retries"); }