analyzePeriodicReturns
Calculate and analyze daily, weekly, or monthly returns for a specified account and trading period using the CCXT MCP Server. Supports customizable intervals and optional symbol filtering for precise insights.
Instructions
Analyze daily and monthly returns for a configured account
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| accountName | Yes | Account name defined in the configuration file (e.g., 'bybit_main') | |
| interval | No | Return calculation interval | daily |
| period | No | Analysis period: '30d', '90d', '180d', or '1y' | 90d |
| symbol | No | Optional trading symbol (e.g., 'BTC/USDT') to filter trades |
Implementation Reference
- src/tools/analysis-tools.ts:262-347 (handler)Primary implementation: server.tool() call that registers the tool, defines input schema with Zod, and provides the async handler function. The handler fetches historical trades using CCXT's fetchMyTrades, determines the time range based on period, and delegates computation to the calculatePeriodicReturns helper.server.tool( "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, }; } } );
- src/tools/analysis-tools.ts:265-283 (schema)Input schema defined using Zod for parameters: accountName (required string), symbol (optional string), period (enum with default), interval (enum with default).{ 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"), },
- src/tools/analysis-tools.ts:681-763 (helper)Helper function that processes trade data, groups by specified interval (daily/weekly/monthly), computes profit per period using simplified P&L calculation, and returns statistics including total profit, average, best/worst periods, and ratio of profitable periods. Uses getWeekNumber for weekly grouping.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 call to registerAnalysisTools within CcxtMcpServer.registerTools(), which in turn registers the analyzePeriodicReturns tool among others.registerAnalysisTools(this.server, this);