Skip to main content
Glama

get_funding_analysis

Analyze funding income across exchanges to track funding arbitrage profitability with cumulative totals, annualized rates, and per-position breakdown.

Instructions

Analyze funding income across exchanges — cumulative totals, annualized rates, and per-arb-position breakdown. Great for tracking funding arb profitability.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
periodNoTime period: 7d, 30d, 90d, or all30d
exchangeNoFilter by exchange
symbolNoFilter by symbol

Implementation Reference

  • The handler implementation for the `get_funding_analysis` tool. It aggregates funding payments and calculates annualized rates and arb position breakdowns.
      "get_funding_analysis",
      "Analyze funding income across exchanges — cumulative totals, annualized rates, and per-arb-position breakdown. Great for tracking funding arb profitability.",
      {
        period: z.string().default("30d").describe("Time period: 7d, 30d, 90d, or all"),
        exchange: z.string().optional().describe("Filter by exchange"),
        symbol: z.string().optional().describe("Filter by symbol"),
      },
      async ({ period, exchange: exFilter, symbol: symFilter }) => {
        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;
    
          interface FundingEntry { exchange: string; symbol: string; payment: number; time: number }
          const allFunding: FundingEntry[] = [];
          const posNotionals = new Map<string, number>();
    
          await Promise.allSettled(exchanges.map(async (ex) => {
            try {
              const adapter = await getOrCreateAdapter(ex);
              const [payments, positions] = await Promise.all([
                adapter.getFundingPayments(200),
                adapter.getPositions(),
              ]);
              for (const p of payments) {
                if (sinceMs && p.time < sinceMs) continue;
                if (symFilter && !p.symbol.toUpperCase().includes(symFilter.toUpperCase())) continue;
                allFunding.push({ exchange: ex, symbol: p.symbol, payment: Number(p.payment), time: p.time });
              }
              for (const p of positions) {
                if (Number(p.size) > 0) posNotionals.set(`${ex}:${p.symbol}`, Math.abs(Number(p.size) * Number(p.markPrice)));
              }
            } catch { /* skip */ }
          }));
    
          allFunding.sort((a, b) => a.time - b.time);
    
          // Aggregate by exchange×symbol
          const byExSym = new Map<string, { exchange: string; symbol: string; total: number; count: number }>();
          let totalFunding = 0;
          for (const f of allFunding) {
            totalFunding += f.payment;
            const key = `${f.exchange}:${f.symbol}`;
            const e = byExSym.get(key) ?? { exchange: f.exchange, symbol: f.symbol, total: 0, count: 0 };
            e.total += f.payment; e.count++;
            byExSym.set(key, e);
          }
    
          const periodDays = sinceMs ? (Date.now() - sinceMs) / 86400000
            : allFunding.length > 1 ? (allFunding[allFunding.length - 1].time - allFunding[0].time) / 86400000 : 1;
    
          // Match to arb positions
          const arbState = loadArbState();
          const arbPositions = (arbState?.positions ?? []).map(pos => {
            const lf = allFunding.filter(f => f.symbol === pos.symbol && f.exchange === pos.longExchange).reduce((s, f) => s + f.payment, 0);
            const sf = allFunding.filter(f => f.symbol === pos.symbol && f.exchange === pos.shortExchange).reduce((s, f) => s + f.payment, 0);
            return { symbol: pos.symbol, mode: pos.mode ?? "perp-perp", longExchange: pos.longExchange, shortExchange: pos.shortExchange, netFunding: lf + sf, daysHeld: Math.max(1, Math.round((Date.now() - new Date(pos.entryTime).getTime()) / 86400000)) };
          }).filter(a => a.netFunding !== 0);
    
          const data = {
            period, periodDays: Math.round(periodDays), totalFunding, totalPayments: allFunding.length,
            byExchangeSymbol: [...byExSym.values()].sort((a, b) => b.total - a.total).map(e => ({
              ...e, annualizedRate: (() => {
                const n = posNotionals.get(`${e.exchange}:${e.symbol}`);
                return n && n > 0 && periodDays > 0 ? (e.total / Math.max(1, periodDays)) * 365 / n * 100 : null;
              })(),
            })),
            arbPositions,
          };
    
          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