Skip to main content
Glama
gtorreal
by gtorreal

get_arbitrage_opportunities

Identify cross-market price differences for a selected cryptocurrency across Buda's Chilean, Colombian, and Peruvian exchanges. Local prices are converted to USDC, and discrepancies above a set percentage are listed.

Instructions

Detects cross-country price discrepancies for a given asset across Buda's CLP, COP, and PEN markets, normalized to USDC. Fetches all relevant tickers, converts each local price to USDC using the current USDC-CLP / USDC-COP / USDC-PEN rates, then computes pairwise discrepancy percentages. Results above threshold_pct are returned sorted by opportunity size. Note: Buda taker fee is 0.8% per leg (~1.6% round-trip) — always deduct fees before acting on any discrepancy. Example: 'Is there an arbitrage opportunity for BTC between Chile and Peru right now?'

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
base_currencyYesBase asset to scan (e.g. 'BTC', 'ETH', 'XRP').
threshold_pctNoMinimum price discrepancy percentage to include in results (default: 0.5). Buda taker fee is 0.8% per leg, so a round-trip requires > 1.6% to be profitable.

Implementation Reference

  • Main handler that fetches all tickers, normalizes local prices to USDC using USDC-CLP/USDC-COP/USDC-PEN rates, computes pairwise discrepancies, filters by threshold, and returns sorted arbitrage opportunities.
    export async function handleArbitrageOpportunities(
      { base_currency, threshold_pct = 0.5 }: ArbitrageInput,
      client: BudaClient,
      cache: MemoryCache,
    ): Promise<{ content: Array<{ type: "text"; text: string }>; isError?: boolean }> {
      const currencyError = validateCurrency(base_currency);
      if (currencyError) {
        return {
          content: [{ type: "text", text: JSON.stringify({ error: currencyError, code: "INVALID_CURRENCY" }) }],
          isError: true,
        };
      }
    
      try {
        const base = base_currency.toUpperCase();
        const data = await cache.getOrFetch<AllTickersResponse>(
          "tickers:all",
          CACHE_TTL.TICKER,
          () => client.get<AllTickersResponse>("/tickers"),
        );
    
        const tickerMap = new Map<string, Ticker>();
        for (const t of data.tickers) {
          tickerMap.set(t.market_id, t);
        }
    
        // Find USDC conversion rates for each fiat currency
        const usdcClpTicker = tickerMap.get("USDC-CLP");
        const usdcCopTicker = tickerMap.get("USDC-COP");
        const usdcPenTicker = tickerMap.get("USDC-PEN");
    
        // Build list of markets for the requested base currency with USDC-normalized prices
        interface MarketPrice {
          market_id: string;
          local_price: number;
          usdc_rate: number;
          price_usdc: number;
        }
    
        const marketPrices: MarketPrice[] = [];
    
        const candidates: Array<{ suffix: string; usdcTicker: Ticker | undefined }> = [
          { suffix: "CLP", usdcTicker: usdcClpTicker },
          { suffix: "COP", usdcTicker: usdcCopTicker },
          { suffix: "PEN", usdcTicker: usdcPenTicker },
        ];
    
        for (const { suffix, usdcTicker } of candidates) {
          const marketId = `${base}-${suffix}`;
          const baseTicker = tickerMap.get(marketId);
    
          if (!baseTicker || !usdcTicker) continue;
    
          const localPrice = parseFloat(baseTicker.last_price[0]);
          const usdcRate = parseFloat(usdcTicker.last_price[0]);
    
          if (isNaN(localPrice) || isNaN(usdcRate) || usdcRate === 0) continue;
    
          marketPrices.push({
            market_id: marketId,
            local_price: localPrice,
            usdc_rate: usdcRate,
            price_usdc: localPrice / usdcRate,
          });
        }
    
        if (marketPrices.length < 2) {
          return {
            content: [
              {
                type: "text",
                text: JSON.stringify({
                  error: `Not enough markets found for base currency '${base}' to compute arbitrage. ` +
                    `Need at least 2 of: ${base}-CLP, ${base}-COP, ${base}-PEN with USDC rates available.`,
                  code: "INSUFFICIENT_MARKETS",
                }),
              },
            ],
            isError: true,
          };
        }
    
        // Compute all pairwise discrepancies
        const opportunities: ArbitrageOpportunity[] = [];
    
        for (let i = 0; i < marketPrices.length; i++) {
          for (let j = i + 1; j < marketPrices.length; j++) {
            const a = marketPrices[i];
            const b = marketPrices[j];
            const minPrice = Math.min(a.price_usdc, b.price_usdc);
            const discrepancyPct = (Math.abs(a.price_usdc - b.price_usdc) / minPrice) * 100;
    
            if (discrepancyPct < threshold_pct) continue;
    
            const higherMarket = a.price_usdc > b.price_usdc ? a.market_id : b.market_id;
            const lowerMarket = a.price_usdc < b.price_usdc ? a.market_id : b.market_id;
    
            opportunities.push({
              market_a: a.market_id,
              market_b: b.market_id,
              price_a_usdc: parseFloat(a.price_usdc.toFixed(4)),
              price_b_usdc: parseFloat(b.price_usdc.toFixed(4)),
              discrepancy_pct: parseFloat(discrepancyPct.toFixed(4)),
              higher_market: higherMarket,
              lower_market: lowerMarket,
            });
          }
        }
    
        opportunities.sort((a, b) => b.discrepancy_pct - a.discrepancy_pct);
    
        const result = {
          base_currency: base,
          threshold_pct,
          markets_analyzed: marketPrices.map((m) => ({
            market_id: m.market_id,
            price_usdc: parseFloat(m.price_usdc.toFixed(4)),
            local_price: m.local_price,
            usdc_rate: m.usdc_rate,
          })),
          opportunities_found: opportunities.length,
          opportunities,
          fees_note:
            "Buda taker fee is 0.8% per leg. A round-trip arbitrage (buy on one market, sell on another) " +
            "costs approximately 1.6% in fees. Only discrepancies well above 1.6% are likely profitable.",
        };
    
        return {
          content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
        };
      } catch (err) {
        const msg = formatApiError(err);
        return {
          content: [{ type: "text", text: JSON.stringify(msg) }],
          isError: true,
        };
      }
    }
  • Schema definition for the 'get_arbitrage_opportunities' tool, defining name, description, and Zod-based inputSchema with base_currency (string, required) and threshold_pct (number, optional, default 0.5).
    export const toolSchema = {
      name: "get_arbitrage_opportunities",
      description:
        "Detects cross-country price discrepancies for a given asset across Buda's CLP, COP, and PEN markets, " +
        "normalized to USDC. Fetches all relevant tickers, converts each local price to USDC using the " +
        "current USDC-CLP / USDC-COP / USDC-PEN rates, then computes pairwise discrepancy percentages. " +
        "Results above threshold_pct are returned sorted by opportunity size. Note: Buda taker fee is 0.8% " +
        "per leg (~1.6% round-trip) — always deduct fees before acting on any discrepancy. " +
        "Example: 'Is there an arbitrage opportunity for BTC between Chile and Peru right now?'",
      inputSchema: {
        type: "object" as const,
        properties: {
          base_currency: {
            type: "string",
            description: "Base asset to scan (e.g. 'BTC', 'ETH', 'XRP').",
          },
          threshold_pct: {
            type: "number",
            description:
              "Minimum price discrepancy percentage to include in results (default: 0.5). " +
              "Buda taker fee is 0.8% per leg, so a round-trip requires > 1.6% to be profitable.",
          },
        },
        required: ["base_currency"],
      },
    };
  • TypeScript interfaces for ArbitrageOpportunity (result shape) and ArbitrageInput (handler parameters).
    interface ArbitrageOpportunity {
      market_a: string;
      market_b: string;
      price_a_usdc: number;
      price_b_usdc: number;
      discrepancy_pct: number;
      higher_market: string;
      lower_market: string;
    }
    
    interface ArbitrageInput {
      base_currency: string;
      threshold_pct?: number;
    }
  • Registration function that calls server.tool() with the tool name, description, Zod-validated args schema, and a callback delegating to handleArbitrageOpportunities.
    export function register(server: McpServer, client: BudaClient, cache: MemoryCache): void {
      server.tool(
        toolSchema.name,
        toolSchema.description,
        {
          base_currency: z
            .string()
            .min(2)
            .max(10)
            .regex(/^[A-Z0-9]+$/i, "Must be 2–10 alphanumeric characters (e.g. 'BTC', 'ETH').")
            .describe("Base asset to scan (e.g. 'BTC', 'ETH', 'XRP')."),
          threshold_pct: z
            .number()
            .min(0)
            .default(0.5)
            .describe(
              "Minimum price discrepancy percentage to include in results (default: 0.5). " +
                "Buda taker fee is 0.8% per leg, so a round-trip requires > 1.6% to be profitable.",
            ),
        },
        (args) => handleArbitrageOpportunities(args, client, cache),
      );
    }
  • src/index.ts:44-52 (registration)
    Tool registration call in the main index.ts which imports the arbitrage module and registers it with the McpServer.
    arbitrage.register(server, client, cache);
    marketSummary.register(server, client, cache);
    simulateOrder.register(server, client, cache);
    quotation.register(server, client);
    positionSize.register(server);
    marketSentiment.register(server, client, cache);
    technicalIndicators.register(server, client);
    banks.register(server, client, cache);
    stableLiquidity.register(server, client, cache);
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden. It explains the internal steps (fetch tickers, convert to USDC, compute discrepancies) and the threshold logic. It also notes the fee deduction requirement. However, it omits potential error conditions or rate limits.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is concise, well-structured, and front-loaded. It covers purpose, method, important notes, and an example in a compact form without redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given no output schema, the description hints at the return format (sorted by opportunity size) and provides enough context for an agent to understand the tool's operation. Minor gaps like exact output structure are acceptable.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, but the description adds valuable context: it explains how threshold_pct relates to profitability given fees, and gives examples for base_currency. This enhances understanding beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool detects cross-country price discrepancies for a given asset across specific markets, normalized to USDC. It uses specific verbs and resources, and the example helps clarify the purpose.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides clear guidance on when to use the tool (to find arbitrage opportunities) and includes important context like fee structure and the need to deduct fees. It doesn't explicitly mention alternatives but the context is sufficient.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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/gtorreal/buda-mcp'

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