Skip to main content
Glama

analyzePeriodicReturns

Calculate and analyze daily or monthly returns for cryptocurrency trading accounts over specified periods to assess performance.

Instructions

Analyze daily and monthly returns for a configured account

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
accountNameYesAccount name defined in the configuration file (e.g., 'bybit_main')
symbolNoOptional trading symbol (e.g., 'BTC/USDT') to filter trades
periodNoAnalysis period: '30d', '90d', '180d', or '1y'90d
intervalNoReturn calculation intervaldaily

Implementation Reference

  • The async handler function that implements the core logic of analyzePeriodicReturns: fetches trades via CCXT, computes since timestamp based on period, calls calculatePeriodicReturns helper, and returns JSON results or error.
    async ({ accountName, symbol, period, interval }) => {
      try {
        const exchange = ccxtServer.getExchangeInstance(accountName);
    
        // fetchMyTrades 메서드가 지원되는지 확인
        if (!exchange.has["fetchMyTrades"]) {
          return {
            content: [
              {
                type: "text",
                text: `Account '${accountName}' (Exchange: ${exchange.id}) does not support fetching personal trades for periodic returns analysis`,
              },
            ],
            isError: true,
          };
        }
    
        // 기간에 따른 since 값 계산
        const now = Date.now();
        let since;
        switch (period) {
          case "30d":
            since = now - 30 * 24 * 60 * 60 * 1000;
            break;
          case "90d":
            since = now - 90 * 24 * 60 * 60 * 1000;
            break;
          case "180d":
            since = now - 180 * 24 * 60 * 60 * 1000;
            break;
          case "1y":
            since = now - 365 * 24 * 60 * 60 * 1000;
            break;
        }
    
        // 거래 내역 가져오기
        const trades = await exchange.fetchMyTrades(symbol, since, undefined);
    
        // 기간별 수익률 계산
        const returns = calculatePeriodicReturns(trades, interval);
    
        return {
          content: [
            {
              type: "text",
              text: JSON.stringify(returns, null, 2),
            },
          ],
        };
      } catch (error) {
        return {
          content: [
            {
              type: "text",
              text: `Error analyzing periodic returns for account '${accountName}': ${
                (error as Error).message
              }`,
            },
          ],
          isError: true,
        };
      }
    }
  • Zod input schema defining parameters for the tool: accountName (string), symbol (optional string), period (enum: '30d', '90d', '180d', '1y'), interval (enum: 'daily', 'weekly', 'monthly').
    {
      accountName: z
        .string()
        .describe(
          "Account name defined in the configuration file (e.g., 'bybit_main')"
        ),
      symbol: z
        .string()
        .optional()
        .describe("Optional trading symbol (e.g., 'BTC/USDT') to filter trades"),
      period: z
        .enum(["30d", "90d", "180d", "1y"])
        .default("90d")
        .describe("Analysis period: '30d', '90d', '180d', or '1y'"),
      interval: z
        .enum(["daily", "weekly", "monthly"])
        .default("daily")
        .describe("Return calculation interval"),
    },
  • MCP server tool registration: server.tool('analyzePeriodicReturns', description, inputSchema, handlerFn) within registerAnalysisTools function.
      "analyzePeriodicReturns",
      "Analyze daily and monthly returns for a configured account",
      {
        accountName: z
          .string()
          .describe(
            "Account name defined in the configuration file (e.g., 'bybit_main')"
          ),
        symbol: z
          .string()
          .optional()
          .describe("Optional trading symbol (e.g., 'BTC/USDT') to filter trades"),
        period: z
          .enum(["30d", "90d", "180d", "1y"])
          .default("90d")
          .describe("Analysis period: '30d', '90d', '180d', or '1y'"),
        interval: z
          .enum(["daily", "weekly", "monthly"])
          .default("daily")
          .describe("Return calculation interval"),
      },
      async ({ accountName, symbol, period, interval }) => {
        try {
          const exchange = ccxtServer.getExchangeInstance(accountName);
    
          // fetchMyTrades 메서드가 지원되는지 확인
          if (!exchange.has["fetchMyTrades"]) {
            return {
              content: [
                {
                  type: "text",
                  text: `Account '${accountName}' (Exchange: ${exchange.id}) does not support fetching personal trades for periodic returns analysis`,
                },
              ],
              isError: true,
            };
          }
    
          // 기간에 따른 since 값 계산
          const now = Date.now();
          let since;
          switch (period) {
            case "30d":
              since = now - 30 * 24 * 60 * 60 * 1000;
              break;
            case "90d":
              since = now - 90 * 24 * 60 * 60 * 1000;
              break;
            case "180d":
              since = now - 180 * 24 * 60 * 60 * 1000;
              break;
            case "1y":
              since = now - 365 * 24 * 60 * 60 * 1000;
              break;
          }
    
          // 거래 내역 가져오기
          const trades = await exchange.fetchMyTrades(symbol, since, undefined);
    
          // 기간별 수익률 계산
          const returns = calculatePeriodicReturns(trades, interval);
    
          return {
            content: [
              {
                type: "text",
                text: JSON.stringify(returns, null, 2),
              },
            ],
          };
        } catch (error) {
          return {
            content: [
              {
                type: "text",
                text: `Error analyzing periodic returns for account '${accountName}': ${
                  (error as Error).message
                }`,
              },
            ],
            isError: true,
          };
        }
      }
    );
  • Helper function calculatePeriodicReturns that groups trades by specified interval (daily/weekly/monthly), computes profit per period, and generates statistics like total profit, average, best/worst periods.
    function calculatePeriodicReturns(trades: any[], interval: string) {
      if (!trades || trades.length === 0) {
        return {
          totalTrades: 0,
          message: "No trades found for the specified period.",
        };
      }
    
      // 거래를 시간순으로 정렬
      trades.sort((a, b) => a.timestamp - b.timestamp);
    
      // 기간별로 그룹화
      const periodicData: Record<string, { profit: number, trades: number }> = {};
      
      trades.forEach(trade => {
        const date = new Date(trade.timestamp);
        let key: string;
        
        switch(interval) {
          case 'weekly':
            // 주차 계산 (ISO 주 - 1부터 53까지)
            const weekOfYear = getWeekNumber(date);
            key = `${date.getFullYear()}-W${weekOfYear}`;
            break;
          case 'monthly':
            // 월 (1월은 0)
            key = `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}`;
            break;
          case 'daily':
          default:
            // 일 (YYYY-MM-DD)
            key = date.toISOString().split('T')[0];
        }
        
        if (!periodicData[key]) {
          periodicData[key] = { profit: 0, trades: 0 };
        }
        
        // 단순화된 손익 계산
        const profit = trade.side === 'buy' 
          ? -(trade.amount * trade.price) 
          : (trade.amount * trade.price);
        
        periodicData[key].profit += profit - (trade.fee?.cost || 0);
        periodicData[key].trades++;
      });
    
      // 결과 처리
      const returns = Object.entries(periodicData).map(([period, data]) => ({
        period,
        profit: data.profit.toFixed(8),
        trades: data.trades
      })).sort((a, b) => a.period.localeCompare(b.period));
      
      // 통계 계산
      const profitValues = returns.map(r => parseFloat(r.profit));
      const totalProfit = profitValues.reduce((sum, profit) => sum + profit, 0);
      const averagePeriodProfit = profitValues.length > 0 
        ? totalProfit / profitValues.length 
        : 0;
      
      const positiveReturns = profitValues.filter(p => p > 0);
      const negativeReturns = profitValues.filter(p => p < 0);
      
      return {
        interval,
        totalPeriods: returns.length,
        totalProfit: totalProfit.toFixed(8),
        averagePeriodProfit: averagePeriodProfit.toFixed(8),
        profitablePeriods: positiveReturns.length,
        lossPeriods: negativeReturns.length,
        profitablePeriodRatio: returns.length > 0 
          ? ((positiveReturns.length / returns.length) * 100).toFixed(2) + '%' 
          : '0%',
        bestPeriod: profitValues.length > 0 
          ? returns[profitValues.indexOf(Math.max(...profitValues))] 
          : null,
        worstPeriod: profitValues.length > 0 
          ? returns[profitValues.indexOf(Math.min(...profitValues))] 
          : null,
        periodicReturns: returns
      };
    }
  • src/server.ts:375-375 (registration)
    Top-level registration call: registerAnalysisTools(this.server, this) in CcxtMcpServer.registerTools() method, which triggers the tool registrations including analyzePeriodicReturns.
    registerAnalysisTools(this.server, this);

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/lazy-dinosaur/ccxt-mcp'

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