Skip to main content
Glama

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
NameRequiredDescriptionDefault
periodNoTime period: 7d, 30d, 90d, or all30d
exchangeNoFilter by exchange

Implementation Reference

  • 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 };
        }
      },
    );

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/hypurrquant/perp-cli'

If you have feedback or need assistance with the MCP directory API, please join our Discord server