get_pnl_analysis
Analyze profit and loss by combining trading fees and funding income across exchanges with daily breakdowns.
Instructions
P&L analysis combining trading fees and funding income — shows net profit/loss by exchange with daily breakdown
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| period | No | Time period: 7d, 30d, 90d, or all | 30d |
| exchange | No | Filter by exchange |
Implementation Reference
- src/mcp-server.ts:1620-1673 (handler)The `get_pnl_analysis` MCP tool implementation that calculates net P&L by aggregating trade history and funding payments across configured exchanges.
"get_pnl_analysis", "P&L analysis combining trading fees and funding income — shows net profit/loss by exchange with daily breakdown", { period: z.string().default("30d").describe("Time period: 7d, 30d, 90d, or all"), exchange: z.string().optional().describe("Filter by exchange"), }, async ({ period, exchange: exFilter }) => { try { const exchanges = exFilter ? [exFilter] : ["pacifica", "hyperliquid", "lighter"]; const periodMatch = period.match(/^(\d+)(d|w|m)$/); const sinceMs = periodMatch ? Date.now() - parseInt(periodMatch[1]) * ({ d: 86400000, w: 604800000, m: 2592000000 }[periodMatch[2]] ?? 86400000) : 0; const byExchange = new Map<string, { trades: number; volume: number; fees: number; funding: number }>(); const dailyMap = new Map<string, { fees: number; funding: number }>(); await Promise.allSettled(exchanges.map(async (ex) => { try { const adapter = await getOrCreateAdapter(ex); const [trades, funding] = await Promise.all([adapter.getTradeHistory(200), adapter.getFundingPayments(200)]); const exData = byExchange.get(ex) ?? { trades: 0, volume: 0, fees: 0, funding: 0 }; for (const t of trades) { if (sinceMs && t.time < sinceMs) continue; const notional = Number(t.price) * Number(t.size); exData.trades++; exData.volume += notional; exData.fees += Number(t.fee); const day = new Date(t.time).toISOString().slice(0, 10); const d = dailyMap.get(day) ?? { fees: 0, funding: 0 }; d.fees += Number(t.fee); dailyMap.set(day, d); } for (const f of funding) { if (sinceMs && f.time < sinceMs) continue; exData.funding += Number(f.payment); const day = new Date(f.time).toISOString().slice(0, 10); const d = dailyMap.get(day) ?? { fees: 0, funding: 0 }; d.funding += Number(f.payment); dailyMap.set(day, d); } byExchange.set(ex, exData); } catch { /* skip */ } })); let totalFees = 0, totalFunding = 0, totalVolume = 0, totalTrades = 0; for (const d of byExchange.values()) { totalFees += d.fees; totalFunding += d.funding; totalVolume += d.volume; totalTrades += d.trades; } const data = { period, totalTrades, totalVolume, totalFees, totalFunding, netPnl: totalFunding - totalFees, byExchange: Object.fromEntries([...byExchange.entries()].map(([ex, d]) => [ex, { ...d, netPnl: d.funding - d.fees }])), daily: [...dailyMap.entries()].sort().map(([date, d]) => ({ date, fees: d.fees, funding: d.funding, net: d.funding - d.fees })), }; return { content: [{ type: "text", text: ok(data) }] }; } catch (e) { return { content: [{ type: "text", text: err(e instanceof Error ? e.message : String(e)) }], isError: true }; } }, );